pyjs8call.settings
JS8Call settings.
Functions for reading and writing settings, including configuration file convenience functions.
1# MIT License 2# 3# Copyright (c) 2022-2024 Simply Equipped 4# 5# Permission is hereby granted, free of charge, to any person obtaining a copy 6# of this software and associated documentation files (the "Software"), to deal 7# in the Software without restriction, including without limitation the rights 8# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9# copies of the Software, and to permit persons to whom the Software is 10# furnished to do so, subject to the following conditions: 11# 12# The above copyright notice and this permission notice shall be included in all 13# copies or substantial portions of the Software. 14# 15# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21# SOFTWARE. 22 23'''JS8Call settings. 24 25Functions for reading and writing settings, including configuration file convenience functions. 26''' 27 28__docformat__ = 'google' 29 30 31import os 32import time 33import configparser 34 35import pyjs8call 36from pyjs8call import Message 37 38 39class Settings: 40 '''Settings function container. 41 42 This class is initilized by pyjs8call.client.Client. 43 ''' 44 45 def __init__(self, client): 46 '''Initialize settings object. 47 48 Returns: 49 pyjs8call.client.Settings: Constructed setting object 50 ''' 51 self._client = client 52 self._daily_restart_schedule = None 53 self.loaded_settings = None 54 '''Loaded settings container (see python3 configparser for more information)''' 55 56 self._settings_map = { 57 'station' : { 58 'callsign': lambda value: self.set_station_callsign(value), 59 'grid': lambda value: self.set_station_grid(value), 60 'speed': lambda value: self.set_speed(value), 61 'freq': lambda value: self.set_freq(value), 62 'frequency': lambda value: self.set_freq(value), 63 'offset': lambda value: self.set_offset(value), 64 'info': lambda value: self.set_station_info(value), 65 'append_pyjs8call_info': lambda value: self.append_pyjs8call_to_station_info(), 66 'daily_restart': lambda value: self.enable_daily_restart(value) if value else self.disable_daily_restart() 67 }, 68 'general': { 69 'groups': lambda value: self.set_groups(value), 70 'multi_speed_decode': lambda value: self.enable_multi_decode() if value else self.disable_multi_decode(), 71 'autoreply_on_at_startup': lambda value: self.enable_autoreply_startup() if value else self.disable_autoreply_startup(), 72 'autoreply_confirmation': lambda value: self.enable_autoreply_confirmation() if value else self.disable_autoreply_confirmation(), 73 'allcall': lambda value: self.enable_allcall() if value else self.disable_allcall(), 74 'reporting': lambda value: self.enable_reporting() if value else self.disable_reporting(), 75 'transmit': lambda value: self.enable_transmit() if value else self.disable_transmit(), 76 'idle_timeout': lambda value: self.set_idle_timeout(value), 77 'distance_units': lambda value: self.set_distance_units(value) 78 }, 79 'heartbeat': { 80 'enable': lambda value: self._client.heartbeat.enable() if value else self._client.heartbeat.disable(), 81 'interval': lambda value: self.set_heartbeat_interval(value), 82 'acknowledgements': lambda value: self.enable_heartbeat_acknowledgements() if value else self.disable_heartbeat_acknowledgements(), 83 'pause_during_qso': lambda value: self.pause_heartbeat_during_qso() if value else self.allow_heartbeat_during_qso() 84 }, 85 'profile': { 86 'profile': lambda value: self.set_profile(value), 87 'set_profile_on_exit': lambda value: self._client.set_profile_on_exit(value) 88 }, 89 'highlight': { 90 'primary_words': lambda value: self.set_primary_highlight_words(value), 91 'secondary_words': lambda value: self.set_secondary_highlight_words(value) 92 }, 93 'spots': { 94 'watch_stations': lambda value: self._client.spots.set_watched_stations(value), 95 'watch_groups': lambda value: self._client.spots.set_watched_groups(value) 96 }, 97 'notifications': { 98 'enable': lambda value: self._client.notifications.enable() if value else self._client.notifications.disable(), 99 'smtp_server': lambda value: self._client.notifications.set_smtp_server(value), 100 'smtp_port': lambda value: self._client.notifications.set_smtp_server_port(value), 101 'smtp_email_address': lambda value: self._client.notifications.set_smtp_email_address(value), 102 'smtp_password': lambda value: self._client.notifications.set_smtp_password(value), 103 'notification_email_address': lambda value: self._client.notifications.set_email_destination(value), 104 'notification_email_subject': lambda value: self._client.notifications.set_email_subject(value), 105 'incoming': lambda value: self._client.notifications.enable_incoming() if value else self._client.notifications.disable_incoming(), 106 'spots': lambda value: self._client.notifications.enable_spots() if value else self._client.notifications.disable_spots(), 107 'station_spots': lambda value: self._client.notifications.enable_station_spots() if value else self._client.notifications.disable_station_spots(), 108 'group_spots': lambda value: self._client.notifications.enable_group_spots() if value else self._client.notifications.disable_group_spots() 109 } 110 } 111 112 # settings set via js8call config file 113 self._pre_start_settings = { 114 'station' : [ 115 'callsign', 116 'speed' 117 ], 118 'general': [ 119 'groups', 120 'multi_speed_decode', 121 'autoreplay_on_at_startup', 122 'autoreply_confirmation', 123 'allcall', 124 'reporting', 125 'transmit', 126 'idle_timeout', 127 'distance_units' 128 ], 129 'heartbeat': [ 130 'interval', 131 'acknowledgements', 132 'pause_during_qso' 133 ], 134 'profile': [ 135 'profile', 136 'set_profile_on_exit' 137 ], 138 'highlight': [ 139 'primary_words', 140 'secondary_words' 141 ], 142 'spots': [ 143 ], 144 'notifications': [ 145 ] 146 } 147 148 def load(self, settings_path): 149 '''Load pyjs8call settings from file. 150 151 The settings file referenced here is specific to pyjs8call, and is not the same as the JS8Call configuration file. The pyjs8call settings file is not required. 152 153 This function must be called before calling *client.start()*. Settings that must be set before or after starting the JS8Call application are handled automatically. Settings that affect the JS8Call config file are set immediately. All other settings are set after *client.start()* is called. 154 155 Example settings file: 156 157 ``` 158 [station] 159 160 callsign=CALL0SIGN 161 grid=EM19 162 speed=normal 163 freq=7078000 164 offset=1750 165 info=QDX 5W, DIPOLE 30FT 166 append_pyjs8call_info=true 167 daily_restart=02:00 168 169 [general] 170 171 groups=@TTP, @AMRRON 172 multi_speed_decode=true 173 autoreply_on_at_startup=true 174 autoreply_confirmation=false 175 allcall=true 176 reporting=true 177 transmit=true 178 idle_timeout=0 179 distance_units=miles 180 181 [heartbeat] 182 183 enable=true 184 interval=15 185 acknowledgements=true 186 pause_during_qso=true 187 188 [profile] 189 190 profile=Default 191 set_profile_on_exit=Default 192 193 [highlight] 194 195 primary_words=KT7RUN, OH8STN 196 secondary_words=simplyequipped 197 198 [spots] 199 200 watch_stations=KT7RUN, OH8STN 201 watch_groups=@TTP, @AMRRON 202 203 [notifications] 204 205 enable=true 206 smtp_server=smtp.gmail.com 207 smtp_port=465 208 smtp_email_address=email@address.com 209 smtp_password=APP_PASSWORD 210 notification_email_address=0123456789@vtext.com 211 notification_email_subject= 212 incoming=true 213 spots=false 214 station_spots=true 215 group_spots=true 216 ``` 217 218 Args: 219 settings_path (str): Relative or absolute path to settings file 220 221 Raises: 222 OSError: Specified settings file not found 223 ''' 224 settings_path = os.path.expanduser(settings_path) 225 settings_path = os.path.abspath(settings_path) 226 227 if not os.path.exists(settings_path): 228 raise FileNotFoundError('Specified settings file not found: {}'.format(settings_path)) 229 230 self.loaded_settings = configparser.ConfigParser(interpolation = None) 231 self.loaded_settings.read(settings_path) 232 233 self.apply_loaded_settings() 234 235 def apply_loaded_settings(self, post_start=False): 236 '''Apply loaded pyjs8call settings. 237 238 This function is called internally by *load_settings()* and *client.start()*. 239 240 Args: 241 post_start (bool): Post start processing if True, pre start processing if False, defaults to False 242 ''' 243 for section in self.loaded_settings.sections(): 244 #skip unsupported section 245 if section not in self._settings_map: 246 continue 247 248 for key, value in self.loaded_settings[section].items(): 249 # skip unsupported key 250 if key not in self._settings_map[section]: 251 continue 252 253 # skip post start settings during pre start processing 254 if not post_start and key in self._pre_start_settings[section]: 255 value = self._parse_loaded_value(value) 256 self._settings_map[section][key](value) 257 258 # skip pre start settings during post start processing 259 if post_start and not key in self._pre_start_settings[section]: 260 value = self._parse_loaded_value(value) 261 self._settings_map[section][key](value) 262 263 def _parse_loaded_value(self, value): 264 '''Parse setting value from string to Python type. 265 266 Args: 267 value (str): Setting value to parse 268 ''' 269 if value is None: 270 return None 271 elif value.lower() in ['true', 'yes']: 272 return True 273 elif value.lower() in ['false', 'no']: 274 return False 275 elif value.lower() in ('none', '', 'nil', 'nill', 'null'): 276 return None 277 elif value.isnumeric(): 278 return int(value) 279 else: 280 return value 281 282 def enable_heartbeat_networking(self): 283 '''Enable heartbeat networking via config file. 284 285 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 286 287 Note that this function disables JS8Call application heartbeat networking via the config file. To enable the pyjs8call heartbeat network messaging module see *client.heartbeat.enable()*. 288 ''' 289 self._client.config.set('Common', 'SubModeHB', 'true') 290 291 def disable_heartbeat_networking(self): 292 '''Disable heartbeat networking via config file. 293 294 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 295 296 Note that this function disables JS8Call application heartbeat networking via the config file. To disable the pyjs8call heartbeat network messaging module see *client.heartbeat.disable()*. 297 ''' 298 self._client.config.set('Common', 'SubModeHB', 'false') 299 300 def heartbeat_networking_enabled(self): 301 '''Whether heartbeat networking enabled in config file. 302 303 Returns: 304 bool: True if heartbeat networking enabled, False otherwise 305 ''' 306 return self._client.config.get('Common', 'SubModeHB', bool) 307 308 def get_heartbeat_interval(self): 309 '''Get heartbeat networking interval. 310 311 Returns: 312 int: Heartbeat networking time interval in minutes 313 ''' 314 return self._client.config.get('Common', 'HBInterval', int) 315 316 def set_heartbeat_interval(self, interval): 317 '''Set the heartbeat networking interval. 318 319 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 320 321 Args: 322 interval (int): New heartbeat networking time interval in minutes 323 324 Returns: 325 int: Current heartbeat networking time interval in minutes 326 ''' 327 return self._client.config.set('Common', 'HBInterval', interval) 328 329 def enable_heartbeat_acknowledgements(self): 330 '''Enable heartbeat acknowledgements via config file. 331 332 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 333 334 Also enables JS8Call heartbeat networking, since heartbeat acknowledgements will not be enabled without heartbeat networking enabled first. This only enables the feature within JS8Call, and does not casue heartbeats to be sent. 335 ''' 336 self.enable_heartbeat_networking() 337 self._client.config.set('Common', 'SubModeHBAck', 'true') 338 339 def disable_heartbeat_acknowledgements(self): 340 '''Disable heartbeat acknowledgements via config file. 341 342 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 343 ''' 344 self._client.config.set('Common', 'SubModeHBAck', 'false') 345 346 def heartbeat_acknowledgements_enabled(self): 347 '''Whether heartbeat acknowledgements enabled in config file. 348 349 Returns: 350 bool: True if heartbeat acknowledgements enabled, False otherwise 351 ''' 352 return self._client.config.get('Common', 'SubModeHBAck', bool) 353 354 def pause_heartbeat_during_qso(self): 355 '''Pause heartbeat messages during QSO via config file. 356 357 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 358 ''' 359 self._client.config.set('Configuration', 'HeartbeatQSOPause', 'true') 360 361 def allow_heartbeat_during_qso(self): 362 '''Allow heartbeat messages during QSO via config file. 363 364 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 365 ''' 366 self._client.config.set('Configuration', 'HeartbeatQSOPause', 'false') 367 368 def heartbeat_during_qso_paused(self): 369 '''Whether heartbeat messages paused during QSO in config file. 370 371 Returns: 372 bool: True if heartbeat messages paused during QSO, False otherwise 373 ''' 374 return self._client.config.get('Configuration', 'HeartbeatQSOPause', bool) 375 376 def enable_multi_decode(self): 377 '''Enable multi-speed decoding via config file. 378 379 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 380 ''' 381 self._client.config.set('Common', 'SubModeHBMultiDecode', 'true') 382 383 def disable_multi_decode(self): 384 '''Disable multi-speed decoding via config file. 385 386 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 387 ''' 388 self._client.config.set('Common', 'SubModeMultiDecode', 'false') 389 390 def multi_decode_enabled(self): 391 '''Whether multi-decode enabled in config file. 392 393 Returns: 394 bool: True if multi-decode enabled, False otherwise 395 ''' 396 return self._client.config.get('Common', 'SubModeMultiDecode', bool) 397 398 def enable_autoreply_startup(self): 399 '''Enable autoreply on start-up via config file. 400 401 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 402 ''' 403 self._client.config.set('Configuration', 'AutoreplyOnAtStartup', 'true') 404 405 def disable_autoreply_startup(self): 406 '''Disable autoreply on start-up via config file. 407 408 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 409 ''' 410 self._client.config.set('Configuration', 'AutoreplyOnAtStartup', 'false') 411 412 def autoreply_startup_enabled(self): 413 '''Whether autoreply enabled at start-up in config file. 414 415 Returns: 416 bool: True if autoreply is enabled at start-up, False otherwise 417 ''' 418 return self._client.config.get('Configuration', 'AutoreplyOnAtStartup', bool) 419 420 def enable_autoreply_confirmation(self): 421 '''Enable autoreply confirmation via config file. 422 423 When running headless the autoreply confirmation dialog box will be inaccessible. 424 425 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 426 ''' 427 self._client.config.set('Configuration', 'AutoreplyConfirmation', 'true') 428 429 def disable_autoreply_confirmation(self): 430 '''Disable autoreply confirmation via config file. 431 432 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 433 ''' 434 self._client.config.set('Configuration', 'AutoreplyConfirmation', 'false') 435 436 def autoreply_confirmation_enabled(self): 437 '''Whether autoreply confirmation enabled in config file. 438 439 Returns: 440 bool: True if autoreply confirmation enabled, False otherwise 441 ''' 442 return self._client.config.get('Configuration', 'AutoreplyConfirmation', bool) 443 444 def enable_allcall(self): 445 '''Enable @ALLCALL participation via config file. 446 447 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 448 ''' 449 self._client.config.set('Configuration', 'AvoidAllcall', 'false') 450 451 def disable_allcall(self): 452 '''Disable @ALLCALL participation via config file. 453 454 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 455 ''' 456 self._client.config.set('Configuration', 'AvoidAllcall', 'true') 457 458 def allcall_enabled(self): 459 '''Whether @ALLCALL participation enabled in config file. 460 461 Returns: 462 bool: True if @ALLCALL participation enabled, False otherwise 463 ''' 464 return not self._client.config.get('Configuration', 'AvoidAllcall', bool) 465 466 def enable_reporting(self): 467 '''Enable PSKReporter reporting via config file. 468 469 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 470 ''' 471 self._client.config.set('Configuration', 'PSKReporter', 'true') 472 473 def disable_reporting(self): 474 '''Disable PSKReporter reporting via config file. 475 476 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 477 ''' 478 self._client.config.set('Configuration', 'PSKReporter', 'false') 479 480 def reporting_enabled(self): 481 '''Whether PSKReporter reporting enabled in config file. 482 483 Returns: 484 bool: True if reporting enabled, False otherwise 485 ''' 486 return self._client.config.get('Configuration', 'PSKReporter', bool) 487 488 def enable_transmit(self): 489 '''Enable JS8Call transmitting via config file. 490 491 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 492 ''' 493 self._client.config.set('Configuration', 'TransmitOFF', 'false') 494 495 def disable_transmit(self): 496 '''Disable JS8Call transmitting via config file. 497 498 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 499 ''' 500 self._client.config.set('Configuration', 'TransmitOFF', 'true') 501 502 def transmit_enabled(self): 503 '''Whether JS8Call transmitting enabled in config file. 504 505 Returns: 506 bool: True if transmitting enabled, False otherwise 507 ''' 508 return not self._client.config.get('Configuration', 'TransmitOFF', bool) 509 510 def get_profile(self): 511 '''Get active JS8call configuration profile via config file. 512 513 This is a convenience function. See pyjs8call.confighandler for other configuration related functions. 514 515 Returns: 516 str: Name of the active configuration profile 517 ''' 518 return self._client.config.get_active_profile() 519 520 def get_profile_list(self): 521 '''Get list of JS8Call configuration profiles via config file. 522 523 This is a convenience function. See pyjs8call.confighandler for other configuration related functions. 524 525 Returns: 526 list: List of configuration profile names 527 ''' 528 return self._client.config.get_profile_list() 529 530 def set_profile(self, profile, restore_on_exit=False, create=False): 531 '''Set active JS8Call configuration profile via config file. 532 533 This is a convenience function. See pyjs8call.confighandler for other configuration related functions. 534 535 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 536 537 Args: 538 profile (str): Profile name 539 restore_on_exit (bool): Restore previous profile on exit, defaults to False 540 create (bool): Create a new profile (copying from Default) if the specified profile does not exist, defaults to False 541 542 Raises: 543 ValueError: Specified profile name does not exist 544 ''' 545 if profile not in self.get_profile_list(): 546 if create: 547 # copy from Default profile 548 self.create_new_profile(profile) 549 else: 550 raise ValueError('Config profile \'' + profile + '\' does not exist') 551 552 if restore_on_exit: 553 self._client._previous_profile = self.get_profile() 554 555 # set profile as active 556 self._client.config.change_profile(profile) 557 558 def create_new_profile(self, new_profile, copy_profile='Default'): 559 '''Create new JS8Call configuration profile. 560 561 This is a convenience function. See pyjs8call.confighandler for other configuration related functions. 562 563 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 564 565 Args: 566 new_profile (str): Name of new profile to create 567 copy_profile (str): Name of an existing profile to copy when creating the new profile, defaults to 'Default' 568 ''' 569 self._client.config.create_new_profile(new_profile, copy_profile) 570 571 def get_groups_list(self): 572 '''Get list of configured JS8Call groups via config file. 573 574 This is a convenience function. See pyjs8call.confighandler for other configuration related functions. 575 576 Returns: 577 list: List of configured group names 578 ''' 579 return self._client.config.get_groups() 580 581 def add_group(self, group): 582 '''Add configured JS8Call group via config file. 583 584 This is a convenience function. See pyjs8call.confighandler for other configuration related functions. 585 586 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 587 588 Args: 589 group (str): Group name 590 ''' 591 self._client.config.add_group(group) 592 593 def remove_group(self, group): 594 '''Remove configured JS8Call group via config file. 595 596 This is a convenience function. See pyjs8call.confighandler for other configuration related functions. 597 598 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 599 600 Args: 601 group (str): Group name 602 ''' 603 self._client.config.remove_group(group) 604 605 def set_groups(self, groups): 606 '''Set configured JS8Call groups via config file. 607 608 This is a convenience function. See pyjs8call.confighandler for other configuration related functions. 609 610 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 611 612 Args: 613 groups (list): List of group names 614 ''' 615 if isinstance(groups, str): 616 groups = groups.split(',') 617 618 groups = ['@' + group.strip(' @') for group in groups] 619 groups = ', '.join(groups) 620 self._client.config.set('Configuration', 'MyGroups', groups) 621 622 def get_primary_highlight_words(self): 623 '''Get primary highlight words via config file. 624 625 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 626 627 Returns: 628 list: Words that should be highlighted on the JC8Call UI 629 ''' 630 words = self._client.config.get('Configuration', 'PrimaryHighlightWords') 631 632 if words == '@Invalid()': 633 words = [] 634 elif words is not None: 635 words = words.split(', ') 636 637 return words 638 639 def set_primary_highlight_words(self, words): 640 '''Set primary highlight words via config file. 641 642 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 643 644 Args: 645 words (list): Words that should be highlighted on the JC8Call UI 646 ''' 647 if isinstance(words, str): 648 words = [word.strip() for word in words.split(',')] 649 650 if len(words) == 0: 651 words = '@Invalid()' 652 else: 653 words = ', '.join(words) 654 655 self._client.config.set('Configuration', 'PrimaryHighlightWords', words) 656 657 def get_secondary_highlight_words(self): 658 '''Get secondary highlight words via config file. 659 660 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 661 662 Returns: 663 list: Words that should be highlighted on the JC8Call UI 664 ''' 665 words = self._client.config.get('Configuration', 'SecondaryHighlightWords') 666 667 if words == '@Invalid()': 668 words = [] 669 elif words is not None: 670 words = words.split(', ') 671 672 return words 673 674 def set_secondary_highlight_words(self, words): 675 '''Set secondary highlight words via config file. 676 677 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 678 679 Args: 680 words (list): Words that should be highlighted on the JC8Call UI 681 ''' 682 if isinstance(words, str): 683 words = [word.strip() for word in words.split(',')] 684 685 if len(words) == 0: 686 words = '@Invalid()' 687 else: 688 words = ', '.join(words) 689 690 self._client.config.set('Configuration', 'SecondaryHighlightWords', words) 691 692 def submode_to_speed(self, submode): 693 '''Map submode *int* to speed *str*. 694 695 | Submode | Speed | 696 | -------- | -------- | 697 | 0 | normal | 698 | 1 | fast | 699 | 2 | turbo | 700 | 4 | slow | 701 | 8 | ultra | 702 703 Args: 704 submode (int): Submode to map to text 705 706 Returns: 707 str: Speed as text 708 ''' 709 # map integer to text 710 speeds = {4:'slow', 0:'normal', 1:'fast', 2:'turbo', 8:'ultra'} 711 712 if submode is not None and int(submode) in speeds: 713 return speeds[int(submode)] 714 else: 715 raise ValueError('Invalid submode \'' + str(submode) + '\'') 716 717 def get_speed(self, update=False): 718 '''Get JS8Call modem speed. 719 720 Possible modem speeds: 721 - slow 722 - normal 723 - fast 724 - turbo 725 - ultra 726 727 Args: 728 update (bool): Update speed if True or use local state if False, defaults to False 729 730 Returns: 731 str: JS8call modem speed setting 732 ''' 733 speed = self._client.js8call.get_state('speed') 734 735 if update or speed is None: 736 msg = Message() 737 msg.set('type', Message.MODE_GET_SPEED) 738 self._client.js8call.send(msg) 739 speed = self._client.js8call.watch('speed') 740 741 return self.submode_to_speed(speed) 742 743 def set_speed(self, speed): 744 '''Set JS8Call modem speed via config file. 745 746 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 747 748 Possible modem speeds: 749 - slow 750 - normal 751 - fast 752 - turbo 753 - ultra 754 755 Args: 756 speed (str): Speed to set 757 758 Returns: 759 str: JS8Call modem speed setting 760 761 ''' 762 if isinstance(speed, str): 763 speeds = {'slow':4, 'normal':0, 'fast':1, 'turbo':2, 'ultra':8} 764 if speed in speeds: 765 speed = speeds[speed] 766 else: 767 raise ValueError('Invalid speed: ' + str(speed)) 768 769 return self._client.config.set('Common', 'SubMode', speed) 770 771# TODO this code sets speed via API, which doesn't work as of JS8Call v2.2 772# msg = Message() 773# msg.set('type', Message.MODE_SET_SPEED) 774# msg.set('params', {'SPEED': speed}) 775# self._client.js8call.send(msg) 776# time.sleep(self._client._set_get_delay) 777# return self.get_speed() 778 779 def get_freq(self, update=False): 780 '''Get JS8Call dial frequency. 781 782 Args: 783 update (bool): Update if True or use local state if False, defaults to False 784 785 Returns: 786 int: Dial frequency in Hz 787 ''' 788 freq = self._client.js8call.get_state('dial') 789 790 if update or freq is None: 791 msg = Message() 792 msg.type = Message.RIG_GET_FREQ 793 self._client.js8call.send(msg) 794 freq = self._client.js8call.watch('dial') 795 796 return freq 797 798 def set_freq(self, freq): 799 '''Set JS8Call dial frequency. 800 801 Args: 802 freq (int): Dial frequency in Hz 803 804 Returns: 805 int: Dial frequency in Hz 806 ''' 807 msg = Message() 808 msg.set('type', Message.RIG_SET_FREQ) 809 msg.set('params', {'DIAL': freq, 'OFFSET': self._client.js8call.get_state('offset')}) 810 self._client.js8call.send(msg) 811 time.sleep(self._client._set_get_delay) 812 return self.get_freq(update = True) 813 814 def get_band(self): 815 '''Get frequency band designation. 816 817 Returns: 818 str: Band designator like \'40m\' or Client.OOB (out-of-band) 819 ''' 820 return Client.freq_to_band(self.get_freq()) 821 822 def get_offset(self, update=False): 823 '''Get JS8Call offset frequency. 824 825 Args: 826 update (bool): Update if True or use local state if False, defaults to False 827 828 Returns: 829 int: Offset frequency in Hz 830 ''' 831 offset = self._client.js8call.get_state('offset') 832 833 if update or offset is None: 834 msg = Message() 835 msg.type = Message.RIG_GET_FREQ 836 self._client.js8call.send(msg) 837 offset = self._client.js8call.watch('offset') 838 839 return offset 840 841 def set_offset(self, offset): 842 '''Set JS8Call offset frequency. 843 844 Args: 845 offset (int): Offset frequency in Hz 846 847 Returns: 848 int: Offset frequency in Hz 849 ''' 850 msg = Message() 851 msg.set('type', Message.RIG_SET_FREQ) 852 msg.set('params', {'DIAL': self._client.js8call.get_state('dial'), 'OFFSET': offset}) 853 self._client.js8call.send(msg) 854 time.sleep(self._client._set_get_delay) 855 return self.get_offset(update = True) 856 857 def get_station_callsign(self, update=False): 858 '''Get JS8Call callsign. 859 860 Args: 861 update (bool): Update if True or use local state if False, defaults to False 862 863 Returns: 864 str: JS8Call configured callsign 865 ''' 866 callsign = self._client.js8call.get_state('callsign') 867 868 if update or callsign is None: 869 msg = Message() 870 msg.type = Message.STATION_GET_CALLSIGN 871 self._client.js8call.send(msg) 872 callsign = self._client.js8call.watch('callsign') 873 874 return callsign 875 876 def set_station_callsign(self, callsign): 877 '''Set JS8Call callsign. 878 879 Callsign must be a maximum of 9 characters and contain at least one number. 880 881 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 882 883 Args: 884 callsign (str): Callsign to set 885 886 Returns: 887 str: JS8Call configured callsign 888 ''' 889 callsign = callsign.upper() 890 891 if len(callsign) <= 9 and any(char.isdigit() for char in callsign): 892 return self._client.config.set('Configuration', 'MyCall', callsign) 893 else: 894 raise ValueError('callsign must be <= 9 characters in length and contain at least 1 number') 895 896 def get_idle_timeout(self): 897 '''Get JS8Call idle timeout. 898 899 Returns: 900 int: Idle timeout in minutes 901 ''' 902 return self._client.config.get('Configuration', 'TxIdleWatchdog', value_type=int) 903 904 def set_idle_timeout(self, timeout): 905 '''Set JS8Call idle timeout. 906 907 If the JS8Call idle timeout is between 1 and 5 minutes, JS8Call will force the idle timeout to 5 minutes on the next application start or exit. 908 909 The maximum idle timeout is 1440 minutes (24 hours). 910 911 Disable the idle timeout by setting it to 0 (zero). 912 913 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 914 915 Args: 916 timeout (int): Idle timeout in minutes 917 918 Returns: 919 int: Current idle timeout in minutes 920 921 Raises: 922 ValueError: Idle timeout must be between 0 and 1440 minutes 923 ''' 924 if timeout < 0 or timeout > 1440: 925 raise ValueError('Idle timeout must be between 0 and 1440 minutes') 926 927 self._client.config.set('Configuration', 'TxIdleWatchdog', timeout) 928 return self.get_idle_timeout() 929 930 def get_distance_units_miles(self): 931 '''Get JS8Call distance unit setting. 932 933 Returns: 934 bool: True if distance units are set to miles, False if km 935 ''' 936 return self._client.config.get('Configuration', 'Miles', bool) 937 938 def set_distance_units_miles(self, units_miles): 939 '''Set JS8Call distance unit setting. 940 941 Args: 942 units_miles (bool): Set units to miles if True, set to km if False 943 944 Returns: 945 bool: True if distance units are set to miles, False if km 946 ''' 947 self._client.config.set('Configuration', 'Miles', str(units_miles).lower()) 948 return self.get_distance_units_miles() 949 950 def get_distance_units(self): 951 '''Get JS8Call distance units. 952 953 Returns: 954 str: Configured distance units: 'mi' or 'km' 955 ''' 956 if self.get_distance_units_miles(): 957 return 'mi' 958 else: 959 return 'km' 960 961 def set_distance_units(self, units): 962 ''' Set JS8Call distance units. 963 964 Args: 965 units (str): Distance units: 'mi', 'miles', 'km', or 'kilometers' 966 967 Returns: 968 str: Configured distance units: 'miles' or 'km' 969 ''' 970 if units.lower() in ['mi', 'miles']: 971 self.set_distance_units_miles(True) 972 return self.get_distance_units() 973 elif units.lower() in ['km', 'kilometers']: 974 self.set_distance_units_miles(False) 975 return self.get_distance_units() 976 else: 977 raise ValueError('Distance units must be: mi, miles, km, or kilometers') 978 979 def get_station_grid(self, update=False): 980 '''Get JS8Call grid square. 981 982 Args: 983 update (bool): Update if True or use local state if False, defaults to False 984 985 Returns: 986 str: JS8Call configured grid square 987 ''' 988 grid = self._client.js8call.get_state('grid') 989 990 if update or grid is None: 991 msg = Message() 992 msg.type = Message.STATION_GET_GRID 993 self._client.js8call.send(msg) 994 grid = self._client.js8call.watch('grid') 995 996 return grid 997 998 def set_station_grid(self, grid): 999 '''Set JS8Call grid square. 1000 1001 Args: 1002 grid (str): Grid square 1003 1004 Returns: 1005 str: JS8Call configured grid square 1006 ''' 1007 grid = grid.upper() 1008 msg = Message() 1009 msg.type = Message.STATION_SET_GRID 1010 msg.value = grid 1011 self._client.js8call.send(msg) 1012 time.sleep(self._client._set_get_delay) 1013 return self.get_station_grid(update = True) 1014 1015 def get_station_info(self, update=False): 1016 '''Get JS8Call station information. 1017 1018 Args: 1019 update (bool): Update if True or use local state if False, defaults to False 1020 1021 Returns: 1022 str: JS8Call configured station information 1023 ''' 1024 info = self._client.js8call.get_state('info') 1025 1026 if update or info is None: 1027 msg = Message() 1028 msg.type = Message.STATION_GET_INFO 1029 self._client.js8call.send(msg) 1030 info = self._client.js8call.watch('info') 1031 1032 return info 1033 1034 def set_station_info(self, info): 1035 '''Set JS8Call station information. 1036 1037 *info* updated via API (if connected) and set in JS8Call configuration file. 1038 1039 Args: 1040 info (str): Station information 1041 1042 Returns: 1043 str: JS8Call configured station information 1044 ''' 1045 if self._client.online: 1046 msg = Message() 1047 msg.type = Message.STATION_SET_INFO 1048 msg.value = info 1049 self._client.js8call.send(msg) 1050 time.sleep(self._client._set_get_delay) 1051 info = self.get_station_info(update = True) 1052 1053 # save to config file to preserve over restart 1054 self._client.config.set('Configuration', 'MyInfo', info) 1055 return info 1056 1057 def append_pyjs8call_to_station_info(self): 1058 '''Append pyjs8call info to station info 1059 1060 A string like ', PYJS8CALL V0.0.0' is appended to the current station info. 1061 Example: 'QRPLABS QDX, 40M DIPOLE 33FT, PYJS8CALL V0.2.2' 1062 1063 If a string like ', PYJS8CALL' or ',PYJS8CALL' is found in the current station info, that substring (and everything after it) is dropped before appending the new pyjs8call info. 1064 1065 Returns: 1066 str: JS8Call configured station information 1067 ''' 1068 info = self.get_station_info().upper() 1069 1070 if ', PYJS8CALL' in info: 1071 info = info.split(', PYJS8CALL')[0] 1072 elif ',PYJS8CALL' in info: 1073 info = info.split(',PYJS8CALL')[0] 1074 1075 info = '{}, PYJS8CALL {}'.format(info, pyjs8call.__version__) 1076 return self.set_station_info(info) 1077 1078 def get_bandwidth(self, speed=None): 1079 '''Get JS8Call signal bandwidth based on modem speed. 1080 1081 Uses JS8Call configured speed if no speed is given. 1082 1083 | Speed | Bandwidth | 1084 | -------- | -------- | 1085 | slow | 25 Hz | 1086 | normal | 50 Hz | 1087 | fast | 80 Hz | 1088 | turbo | 160 Hz | 1089 | ultra | 250 Hz | 1090 1091 Args: 1092 speed (str): Speed setting, defaults to None 1093 1094 Returns: 1095 int: Bandwidth of JS8Call signal 1096 ''' 1097 if speed is None: 1098 speed = self.get_speed() 1099 elif isinstance(speed, int): 1100 speed = self.submode_to_speed(speed) 1101 1102 bandwidths = {'slow':25, 'normal':50, 'fast':80, 'turbo':160, 'ultra':250} 1103 1104 if speed in bandwidths: 1105 return bandwidths[speed] 1106 else: 1107 raise ValueError('Invalid speed \'' + speed + '\'') 1108 1109 def get_window_duration(self, speed=None): 1110 '''Get JS8Call rx/tx window duration based on modem speed. 1111 1112 Uses JS8Call configured speed if no speed is given. 1113 1114 | Speed | Duration | 1115 | -------- | -------- | 1116 | slow | 30 seconds | 1117 | normal | 15 seconds | 1118 | fast | 10 seconds | 1119 | turbo | 6 seconds | 1120 | ultra | 4 seconds | 1121 1122 Args: 1123 speed (str): Speed setting, defaults to None 1124 1125 Returns: 1126 int: Duration of JS8Call rx/tx window in seconds 1127 ''' 1128 if speed is None: 1129 speed = self.get_speed() 1130 elif isinstance(speed, int): 1131 speed = self.submode_to_speed(speed) 1132 1133 duration = {'slow': 30, 'normal': 15, 'fast': 10, 'turbo': 6, 'ultra':4} 1134 return duration[speed] 1135 1136 def enable_daily_restart(self, restart_time='02:00'): 1137 '''Enable daily JS8Call restart at specified time. 1138 1139 The intended use of this function is to allow the removal of the *timer.out* file, which grows in size until it consumes all available disk space. This file cannot be removed while the application is running, but is automatically removed during the pyjs8call restart process. 1140 1141 This function adds a schedule entry. See *pyjs8call.schedulemonitor* for more information. 1142 1143 Args: 1144 restart_time (str): Local restart time in 24-hour format (ex. '23:30'), defaults to '02:00' 1145 ''' 1146 # add schedule entry to restart application daily with no settings changes 1147 self._daily_restart_schedule = self._client.schedule.add(restart_time, restart=True) 1148 1149 def disable_daily_restart(self): 1150 '''Disable daily JS8Call restart. 1151 1152 This function removes the schedule entry created by *enable_daily_restart*. See *pyjs8call.schedulemonitor* for more information. 1153 ''' 1154 if self._daily_restart_schedule is None: 1155 return 1156 1157 self._client.schedule.remove(self._daily_restart_schedule.dict()['time'], schedule=self._daily_restart_schedule) 1158 self._daily_restart_schedule = None 1159 1160 def daily_restart_enabled(self): 1161 '''Whether daily JS8Call restart is enabled. 1162 1163 Returns: 1164 bool: True if associated schedule entry is set, False otherwise 1165 ''' 1166 if self._daily_restart_schedule is not None: 1167 return True 1168 else: 1169 return False 1170 1171 def get_daily_restart_time(self): 1172 '''Get daily JS8Call restart time. 1173 1174 Returns: 1175 str or None: Local restart time in 24-hour format (ex. '23:30'), or None if not enabled 1176 ''' 1177 if self._daily_restart_schedule is None: 1178 return 1179 1180 return self._daily_restart_schedule.start.strftime('%H:%M') 1181
40class Settings: 41 '''Settings function container. 42 43 This class is initilized by pyjs8call.client.Client. 44 ''' 45 46 def __init__(self, client): 47 '''Initialize settings object. 48 49 Returns: 50 pyjs8call.client.Settings: Constructed setting object 51 ''' 52 self._client = client 53 self._daily_restart_schedule = None 54 self.loaded_settings = None 55 '''Loaded settings container (see python3 configparser for more information)''' 56 57 self._settings_map = { 58 'station' : { 59 'callsign': lambda value: self.set_station_callsign(value), 60 'grid': lambda value: self.set_station_grid(value), 61 'speed': lambda value: self.set_speed(value), 62 'freq': lambda value: self.set_freq(value), 63 'frequency': lambda value: self.set_freq(value), 64 'offset': lambda value: self.set_offset(value), 65 'info': lambda value: self.set_station_info(value), 66 'append_pyjs8call_info': lambda value: self.append_pyjs8call_to_station_info(), 67 'daily_restart': lambda value: self.enable_daily_restart(value) if value else self.disable_daily_restart() 68 }, 69 'general': { 70 'groups': lambda value: self.set_groups(value), 71 'multi_speed_decode': lambda value: self.enable_multi_decode() if value else self.disable_multi_decode(), 72 'autoreply_on_at_startup': lambda value: self.enable_autoreply_startup() if value else self.disable_autoreply_startup(), 73 'autoreply_confirmation': lambda value: self.enable_autoreply_confirmation() if value else self.disable_autoreply_confirmation(), 74 'allcall': lambda value: self.enable_allcall() if value else self.disable_allcall(), 75 'reporting': lambda value: self.enable_reporting() if value else self.disable_reporting(), 76 'transmit': lambda value: self.enable_transmit() if value else self.disable_transmit(), 77 'idle_timeout': lambda value: self.set_idle_timeout(value), 78 'distance_units': lambda value: self.set_distance_units(value) 79 }, 80 'heartbeat': { 81 'enable': lambda value: self._client.heartbeat.enable() if value else self._client.heartbeat.disable(), 82 'interval': lambda value: self.set_heartbeat_interval(value), 83 'acknowledgements': lambda value: self.enable_heartbeat_acknowledgements() if value else self.disable_heartbeat_acknowledgements(), 84 'pause_during_qso': lambda value: self.pause_heartbeat_during_qso() if value else self.allow_heartbeat_during_qso() 85 }, 86 'profile': { 87 'profile': lambda value: self.set_profile(value), 88 'set_profile_on_exit': lambda value: self._client.set_profile_on_exit(value) 89 }, 90 'highlight': { 91 'primary_words': lambda value: self.set_primary_highlight_words(value), 92 'secondary_words': lambda value: self.set_secondary_highlight_words(value) 93 }, 94 'spots': { 95 'watch_stations': lambda value: self._client.spots.set_watched_stations(value), 96 'watch_groups': lambda value: self._client.spots.set_watched_groups(value) 97 }, 98 'notifications': { 99 'enable': lambda value: self._client.notifications.enable() if value else self._client.notifications.disable(), 100 'smtp_server': lambda value: self._client.notifications.set_smtp_server(value), 101 'smtp_port': lambda value: self._client.notifications.set_smtp_server_port(value), 102 'smtp_email_address': lambda value: self._client.notifications.set_smtp_email_address(value), 103 'smtp_password': lambda value: self._client.notifications.set_smtp_password(value), 104 'notification_email_address': lambda value: self._client.notifications.set_email_destination(value), 105 'notification_email_subject': lambda value: self._client.notifications.set_email_subject(value), 106 'incoming': lambda value: self._client.notifications.enable_incoming() if value else self._client.notifications.disable_incoming(), 107 'spots': lambda value: self._client.notifications.enable_spots() if value else self._client.notifications.disable_spots(), 108 'station_spots': lambda value: self._client.notifications.enable_station_spots() if value else self._client.notifications.disable_station_spots(), 109 'group_spots': lambda value: self._client.notifications.enable_group_spots() if value else self._client.notifications.disable_group_spots() 110 } 111 } 112 113 # settings set via js8call config file 114 self._pre_start_settings = { 115 'station' : [ 116 'callsign', 117 'speed' 118 ], 119 'general': [ 120 'groups', 121 'multi_speed_decode', 122 'autoreplay_on_at_startup', 123 'autoreply_confirmation', 124 'allcall', 125 'reporting', 126 'transmit', 127 'idle_timeout', 128 'distance_units' 129 ], 130 'heartbeat': [ 131 'interval', 132 'acknowledgements', 133 'pause_during_qso' 134 ], 135 'profile': [ 136 'profile', 137 'set_profile_on_exit' 138 ], 139 'highlight': [ 140 'primary_words', 141 'secondary_words' 142 ], 143 'spots': [ 144 ], 145 'notifications': [ 146 ] 147 } 148 149 def load(self, settings_path): 150 '''Load pyjs8call settings from file. 151 152 The settings file referenced here is specific to pyjs8call, and is not the same as the JS8Call configuration file. The pyjs8call settings file is not required. 153 154 This function must be called before calling *client.start()*. Settings that must be set before or after starting the JS8Call application are handled automatically. Settings that affect the JS8Call config file are set immediately. All other settings are set after *client.start()* is called. 155 156 Example settings file: 157 158 ``` 159 [station] 160 161 callsign=CALL0SIGN 162 grid=EM19 163 speed=normal 164 freq=7078000 165 offset=1750 166 info=QDX 5W, DIPOLE 30FT 167 append_pyjs8call_info=true 168 daily_restart=02:00 169 170 [general] 171 172 groups=@TTP, @AMRRON 173 multi_speed_decode=true 174 autoreply_on_at_startup=true 175 autoreply_confirmation=false 176 allcall=true 177 reporting=true 178 transmit=true 179 idle_timeout=0 180 distance_units=miles 181 182 [heartbeat] 183 184 enable=true 185 interval=15 186 acknowledgements=true 187 pause_during_qso=true 188 189 [profile] 190 191 profile=Default 192 set_profile_on_exit=Default 193 194 [highlight] 195 196 primary_words=KT7RUN, OH8STN 197 secondary_words=simplyequipped 198 199 [spots] 200 201 watch_stations=KT7RUN, OH8STN 202 watch_groups=@TTP, @AMRRON 203 204 [notifications] 205 206 enable=true 207 smtp_server=smtp.gmail.com 208 smtp_port=465 209 smtp_email_address=email@address.com 210 smtp_password=APP_PASSWORD 211 notification_email_address=0123456789@vtext.com 212 notification_email_subject= 213 incoming=true 214 spots=false 215 station_spots=true 216 group_spots=true 217 ``` 218 219 Args: 220 settings_path (str): Relative or absolute path to settings file 221 222 Raises: 223 OSError: Specified settings file not found 224 ''' 225 settings_path = os.path.expanduser(settings_path) 226 settings_path = os.path.abspath(settings_path) 227 228 if not os.path.exists(settings_path): 229 raise FileNotFoundError('Specified settings file not found: {}'.format(settings_path)) 230 231 self.loaded_settings = configparser.ConfigParser(interpolation = None) 232 self.loaded_settings.read(settings_path) 233 234 self.apply_loaded_settings() 235 236 def apply_loaded_settings(self, post_start=False): 237 '''Apply loaded pyjs8call settings. 238 239 This function is called internally by *load_settings()* and *client.start()*. 240 241 Args: 242 post_start (bool): Post start processing if True, pre start processing if False, defaults to False 243 ''' 244 for section in self.loaded_settings.sections(): 245 #skip unsupported section 246 if section not in self._settings_map: 247 continue 248 249 for key, value in self.loaded_settings[section].items(): 250 # skip unsupported key 251 if key not in self._settings_map[section]: 252 continue 253 254 # skip post start settings during pre start processing 255 if not post_start and key in self._pre_start_settings[section]: 256 value = self._parse_loaded_value(value) 257 self._settings_map[section][key](value) 258 259 # skip pre start settings during post start processing 260 if post_start and not key in self._pre_start_settings[section]: 261 value = self._parse_loaded_value(value) 262 self._settings_map[section][key](value) 263 264 def _parse_loaded_value(self, value): 265 '''Parse setting value from string to Python type. 266 267 Args: 268 value (str): Setting value to parse 269 ''' 270 if value is None: 271 return None 272 elif value.lower() in ['true', 'yes']: 273 return True 274 elif value.lower() in ['false', 'no']: 275 return False 276 elif value.lower() in ('none', '', 'nil', 'nill', 'null'): 277 return None 278 elif value.isnumeric(): 279 return int(value) 280 else: 281 return value 282 283 def enable_heartbeat_networking(self): 284 '''Enable heartbeat networking via config file. 285 286 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 287 288 Note that this function disables JS8Call application heartbeat networking via the config file. To enable the pyjs8call heartbeat network messaging module see *client.heartbeat.enable()*. 289 ''' 290 self._client.config.set('Common', 'SubModeHB', 'true') 291 292 def disable_heartbeat_networking(self): 293 '''Disable heartbeat networking via config file. 294 295 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 296 297 Note that this function disables JS8Call application heartbeat networking via the config file. To disable the pyjs8call heartbeat network messaging module see *client.heartbeat.disable()*. 298 ''' 299 self._client.config.set('Common', 'SubModeHB', 'false') 300 301 def heartbeat_networking_enabled(self): 302 '''Whether heartbeat networking enabled in config file. 303 304 Returns: 305 bool: True if heartbeat networking enabled, False otherwise 306 ''' 307 return self._client.config.get('Common', 'SubModeHB', bool) 308 309 def get_heartbeat_interval(self): 310 '''Get heartbeat networking interval. 311 312 Returns: 313 int: Heartbeat networking time interval in minutes 314 ''' 315 return self._client.config.get('Common', 'HBInterval', int) 316 317 def set_heartbeat_interval(self, interval): 318 '''Set the heartbeat networking interval. 319 320 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 321 322 Args: 323 interval (int): New heartbeat networking time interval in minutes 324 325 Returns: 326 int: Current heartbeat networking time interval in minutes 327 ''' 328 return self._client.config.set('Common', 'HBInterval', interval) 329 330 def enable_heartbeat_acknowledgements(self): 331 '''Enable heartbeat acknowledgements via config file. 332 333 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 334 335 Also enables JS8Call heartbeat networking, since heartbeat acknowledgements will not be enabled without heartbeat networking enabled first. This only enables the feature within JS8Call, and does not casue heartbeats to be sent. 336 ''' 337 self.enable_heartbeat_networking() 338 self._client.config.set('Common', 'SubModeHBAck', 'true') 339 340 def disable_heartbeat_acknowledgements(self): 341 '''Disable heartbeat acknowledgements via config file. 342 343 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 344 ''' 345 self._client.config.set('Common', 'SubModeHBAck', 'false') 346 347 def heartbeat_acknowledgements_enabled(self): 348 '''Whether heartbeat acknowledgements enabled in config file. 349 350 Returns: 351 bool: True if heartbeat acknowledgements enabled, False otherwise 352 ''' 353 return self._client.config.get('Common', 'SubModeHBAck', bool) 354 355 def pause_heartbeat_during_qso(self): 356 '''Pause heartbeat messages during QSO via config file. 357 358 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 359 ''' 360 self._client.config.set('Configuration', 'HeartbeatQSOPause', 'true') 361 362 def allow_heartbeat_during_qso(self): 363 '''Allow heartbeat messages during QSO via config file. 364 365 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 366 ''' 367 self._client.config.set('Configuration', 'HeartbeatQSOPause', 'false') 368 369 def heartbeat_during_qso_paused(self): 370 '''Whether heartbeat messages paused during QSO in config file. 371 372 Returns: 373 bool: True if heartbeat messages paused during QSO, False otherwise 374 ''' 375 return self._client.config.get('Configuration', 'HeartbeatQSOPause', bool) 376 377 def enable_multi_decode(self): 378 '''Enable multi-speed decoding via config file. 379 380 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 381 ''' 382 self._client.config.set('Common', 'SubModeHBMultiDecode', 'true') 383 384 def disable_multi_decode(self): 385 '''Disable multi-speed decoding via config file. 386 387 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 388 ''' 389 self._client.config.set('Common', 'SubModeMultiDecode', 'false') 390 391 def multi_decode_enabled(self): 392 '''Whether multi-decode enabled in config file. 393 394 Returns: 395 bool: True if multi-decode enabled, False otherwise 396 ''' 397 return self._client.config.get('Common', 'SubModeMultiDecode', bool) 398 399 def enable_autoreply_startup(self): 400 '''Enable autoreply on start-up via config file. 401 402 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 403 ''' 404 self._client.config.set('Configuration', 'AutoreplyOnAtStartup', 'true') 405 406 def disable_autoreply_startup(self): 407 '''Disable autoreply on start-up via config file. 408 409 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 410 ''' 411 self._client.config.set('Configuration', 'AutoreplyOnAtStartup', 'false') 412 413 def autoreply_startup_enabled(self): 414 '''Whether autoreply enabled at start-up in config file. 415 416 Returns: 417 bool: True if autoreply is enabled at start-up, False otherwise 418 ''' 419 return self._client.config.get('Configuration', 'AutoreplyOnAtStartup', bool) 420 421 def enable_autoreply_confirmation(self): 422 '''Enable autoreply confirmation via config file. 423 424 When running headless the autoreply confirmation dialog box will be inaccessible. 425 426 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 427 ''' 428 self._client.config.set('Configuration', 'AutoreplyConfirmation', 'true') 429 430 def disable_autoreply_confirmation(self): 431 '''Disable autoreply confirmation via config file. 432 433 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 434 ''' 435 self._client.config.set('Configuration', 'AutoreplyConfirmation', 'false') 436 437 def autoreply_confirmation_enabled(self): 438 '''Whether autoreply confirmation enabled in config file. 439 440 Returns: 441 bool: True if autoreply confirmation enabled, False otherwise 442 ''' 443 return self._client.config.get('Configuration', 'AutoreplyConfirmation', bool) 444 445 def enable_allcall(self): 446 '''Enable @ALLCALL participation via config file. 447 448 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 449 ''' 450 self._client.config.set('Configuration', 'AvoidAllcall', 'false') 451 452 def disable_allcall(self): 453 '''Disable @ALLCALL participation via config file. 454 455 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 456 ''' 457 self._client.config.set('Configuration', 'AvoidAllcall', 'true') 458 459 def allcall_enabled(self): 460 '''Whether @ALLCALL participation enabled in config file. 461 462 Returns: 463 bool: True if @ALLCALL participation enabled, False otherwise 464 ''' 465 return not self._client.config.get('Configuration', 'AvoidAllcall', bool) 466 467 def enable_reporting(self): 468 '''Enable PSKReporter reporting via config file. 469 470 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 471 ''' 472 self._client.config.set('Configuration', 'PSKReporter', 'true') 473 474 def disable_reporting(self): 475 '''Disable PSKReporter reporting via config file. 476 477 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 478 ''' 479 self._client.config.set('Configuration', 'PSKReporter', 'false') 480 481 def reporting_enabled(self): 482 '''Whether PSKReporter reporting enabled in config file. 483 484 Returns: 485 bool: True if reporting enabled, False otherwise 486 ''' 487 return self._client.config.get('Configuration', 'PSKReporter', bool) 488 489 def enable_transmit(self): 490 '''Enable JS8Call transmitting via config file. 491 492 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 493 ''' 494 self._client.config.set('Configuration', 'TransmitOFF', 'false') 495 496 def disable_transmit(self): 497 '''Disable JS8Call transmitting via config file. 498 499 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 500 ''' 501 self._client.config.set('Configuration', 'TransmitOFF', 'true') 502 503 def transmit_enabled(self): 504 '''Whether JS8Call transmitting enabled in config file. 505 506 Returns: 507 bool: True if transmitting enabled, False otherwise 508 ''' 509 return not self._client.config.get('Configuration', 'TransmitOFF', bool) 510 511 def get_profile(self): 512 '''Get active JS8call configuration profile via config file. 513 514 This is a convenience function. See pyjs8call.confighandler for other configuration related functions. 515 516 Returns: 517 str: Name of the active configuration profile 518 ''' 519 return self._client.config.get_active_profile() 520 521 def get_profile_list(self): 522 '''Get list of JS8Call configuration profiles via config file. 523 524 This is a convenience function. See pyjs8call.confighandler for other configuration related functions. 525 526 Returns: 527 list: List of configuration profile names 528 ''' 529 return self._client.config.get_profile_list() 530 531 def set_profile(self, profile, restore_on_exit=False, create=False): 532 '''Set active JS8Call configuration profile via config file. 533 534 This is a convenience function. See pyjs8call.confighandler for other configuration related functions. 535 536 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 537 538 Args: 539 profile (str): Profile name 540 restore_on_exit (bool): Restore previous profile on exit, defaults to False 541 create (bool): Create a new profile (copying from Default) if the specified profile does not exist, defaults to False 542 543 Raises: 544 ValueError: Specified profile name does not exist 545 ''' 546 if profile not in self.get_profile_list(): 547 if create: 548 # copy from Default profile 549 self.create_new_profile(profile) 550 else: 551 raise ValueError('Config profile \'' + profile + '\' does not exist') 552 553 if restore_on_exit: 554 self._client._previous_profile = self.get_profile() 555 556 # set profile as active 557 self._client.config.change_profile(profile) 558 559 def create_new_profile(self, new_profile, copy_profile='Default'): 560 '''Create new JS8Call configuration profile. 561 562 This is a convenience function. See pyjs8call.confighandler for other configuration related functions. 563 564 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 565 566 Args: 567 new_profile (str): Name of new profile to create 568 copy_profile (str): Name of an existing profile to copy when creating the new profile, defaults to 'Default' 569 ''' 570 self._client.config.create_new_profile(new_profile, copy_profile) 571 572 def get_groups_list(self): 573 '''Get list of configured JS8Call groups via config file. 574 575 This is a convenience function. See pyjs8call.confighandler for other configuration related functions. 576 577 Returns: 578 list: List of configured group names 579 ''' 580 return self._client.config.get_groups() 581 582 def add_group(self, group): 583 '''Add configured JS8Call group via config file. 584 585 This is a convenience function. See pyjs8call.confighandler for other configuration related functions. 586 587 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 588 589 Args: 590 group (str): Group name 591 ''' 592 self._client.config.add_group(group) 593 594 def remove_group(self, group): 595 '''Remove configured JS8Call group via config file. 596 597 This is a convenience function. See pyjs8call.confighandler for other configuration related functions. 598 599 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 600 601 Args: 602 group (str): Group name 603 ''' 604 self._client.config.remove_group(group) 605 606 def set_groups(self, groups): 607 '''Set configured JS8Call groups via config file. 608 609 This is a convenience function. See pyjs8call.confighandler for other configuration related functions. 610 611 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 612 613 Args: 614 groups (list): List of group names 615 ''' 616 if isinstance(groups, str): 617 groups = groups.split(',') 618 619 groups = ['@' + group.strip(' @') for group in groups] 620 groups = ', '.join(groups) 621 self._client.config.set('Configuration', 'MyGroups', groups) 622 623 def get_primary_highlight_words(self): 624 '''Get primary highlight words via config file. 625 626 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 627 628 Returns: 629 list: Words that should be highlighted on the JC8Call UI 630 ''' 631 words = self._client.config.get('Configuration', 'PrimaryHighlightWords') 632 633 if words == '@Invalid()': 634 words = [] 635 elif words is not None: 636 words = words.split(', ') 637 638 return words 639 640 def set_primary_highlight_words(self, words): 641 '''Set primary highlight words via config file. 642 643 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 644 645 Args: 646 words (list): Words that should be highlighted on the JC8Call UI 647 ''' 648 if isinstance(words, str): 649 words = [word.strip() for word in words.split(',')] 650 651 if len(words) == 0: 652 words = '@Invalid()' 653 else: 654 words = ', '.join(words) 655 656 self._client.config.set('Configuration', 'PrimaryHighlightWords', words) 657 658 def get_secondary_highlight_words(self): 659 '''Get secondary highlight words via config file. 660 661 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 662 663 Returns: 664 list: Words that should be highlighted on the JC8Call UI 665 ''' 666 words = self._client.config.get('Configuration', 'SecondaryHighlightWords') 667 668 if words == '@Invalid()': 669 words = [] 670 elif words is not None: 671 words = words.split(', ') 672 673 return words 674 675 def set_secondary_highlight_words(self, words): 676 '''Set secondary highlight words via config file. 677 678 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 679 680 Args: 681 words (list): Words that should be highlighted on the JC8Call UI 682 ''' 683 if isinstance(words, str): 684 words = [word.strip() for word in words.split(',')] 685 686 if len(words) == 0: 687 words = '@Invalid()' 688 else: 689 words = ', '.join(words) 690 691 self._client.config.set('Configuration', 'SecondaryHighlightWords', words) 692 693 def submode_to_speed(self, submode): 694 '''Map submode *int* to speed *str*. 695 696 | Submode | Speed | 697 | -------- | -------- | 698 | 0 | normal | 699 | 1 | fast | 700 | 2 | turbo | 701 | 4 | slow | 702 | 8 | ultra | 703 704 Args: 705 submode (int): Submode to map to text 706 707 Returns: 708 str: Speed as text 709 ''' 710 # map integer to text 711 speeds = {4:'slow', 0:'normal', 1:'fast', 2:'turbo', 8:'ultra'} 712 713 if submode is not None and int(submode) in speeds: 714 return speeds[int(submode)] 715 else: 716 raise ValueError('Invalid submode \'' + str(submode) + '\'') 717 718 def get_speed(self, update=False): 719 '''Get JS8Call modem speed. 720 721 Possible modem speeds: 722 - slow 723 - normal 724 - fast 725 - turbo 726 - ultra 727 728 Args: 729 update (bool): Update speed if True or use local state if False, defaults to False 730 731 Returns: 732 str: JS8call modem speed setting 733 ''' 734 speed = self._client.js8call.get_state('speed') 735 736 if update or speed is None: 737 msg = Message() 738 msg.set('type', Message.MODE_GET_SPEED) 739 self._client.js8call.send(msg) 740 speed = self._client.js8call.watch('speed') 741 742 return self.submode_to_speed(speed) 743 744 def set_speed(self, speed): 745 '''Set JS8Call modem speed via config file. 746 747 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 748 749 Possible modem speeds: 750 - slow 751 - normal 752 - fast 753 - turbo 754 - ultra 755 756 Args: 757 speed (str): Speed to set 758 759 Returns: 760 str: JS8Call modem speed setting 761 762 ''' 763 if isinstance(speed, str): 764 speeds = {'slow':4, 'normal':0, 'fast':1, 'turbo':2, 'ultra':8} 765 if speed in speeds: 766 speed = speeds[speed] 767 else: 768 raise ValueError('Invalid speed: ' + str(speed)) 769 770 return self._client.config.set('Common', 'SubMode', speed) 771 772# TODO this code sets speed via API, which doesn't work as of JS8Call v2.2 773# msg = Message() 774# msg.set('type', Message.MODE_SET_SPEED) 775# msg.set('params', {'SPEED': speed}) 776# self._client.js8call.send(msg) 777# time.sleep(self._client._set_get_delay) 778# return self.get_speed() 779 780 def get_freq(self, update=False): 781 '''Get JS8Call dial frequency. 782 783 Args: 784 update (bool): Update if True or use local state if False, defaults to False 785 786 Returns: 787 int: Dial frequency in Hz 788 ''' 789 freq = self._client.js8call.get_state('dial') 790 791 if update or freq is None: 792 msg = Message() 793 msg.type = Message.RIG_GET_FREQ 794 self._client.js8call.send(msg) 795 freq = self._client.js8call.watch('dial') 796 797 return freq 798 799 def set_freq(self, freq): 800 '''Set JS8Call dial frequency. 801 802 Args: 803 freq (int): Dial frequency in Hz 804 805 Returns: 806 int: Dial frequency in Hz 807 ''' 808 msg = Message() 809 msg.set('type', Message.RIG_SET_FREQ) 810 msg.set('params', {'DIAL': freq, 'OFFSET': self._client.js8call.get_state('offset')}) 811 self._client.js8call.send(msg) 812 time.sleep(self._client._set_get_delay) 813 return self.get_freq(update = True) 814 815 def get_band(self): 816 '''Get frequency band designation. 817 818 Returns: 819 str: Band designator like \'40m\' or Client.OOB (out-of-band) 820 ''' 821 return Client.freq_to_band(self.get_freq()) 822 823 def get_offset(self, update=False): 824 '''Get JS8Call offset frequency. 825 826 Args: 827 update (bool): Update if True or use local state if False, defaults to False 828 829 Returns: 830 int: Offset frequency in Hz 831 ''' 832 offset = self._client.js8call.get_state('offset') 833 834 if update or offset is None: 835 msg = Message() 836 msg.type = Message.RIG_GET_FREQ 837 self._client.js8call.send(msg) 838 offset = self._client.js8call.watch('offset') 839 840 return offset 841 842 def set_offset(self, offset): 843 '''Set JS8Call offset frequency. 844 845 Args: 846 offset (int): Offset frequency in Hz 847 848 Returns: 849 int: Offset frequency in Hz 850 ''' 851 msg = Message() 852 msg.set('type', Message.RIG_SET_FREQ) 853 msg.set('params', {'DIAL': self._client.js8call.get_state('dial'), 'OFFSET': offset}) 854 self._client.js8call.send(msg) 855 time.sleep(self._client._set_get_delay) 856 return self.get_offset(update = True) 857 858 def get_station_callsign(self, update=False): 859 '''Get JS8Call callsign. 860 861 Args: 862 update (bool): Update if True or use local state if False, defaults to False 863 864 Returns: 865 str: JS8Call configured callsign 866 ''' 867 callsign = self._client.js8call.get_state('callsign') 868 869 if update or callsign is None: 870 msg = Message() 871 msg.type = Message.STATION_GET_CALLSIGN 872 self._client.js8call.send(msg) 873 callsign = self._client.js8call.watch('callsign') 874 875 return callsign 876 877 def set_station_callsign(self, callsign): 878 '''Set JS8Call callsign. 879 880 Callsign must be a maximum of 9 characters and contain at least one number. 881 882 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 883 884 Args: 885 callsign (str): Callsign to set 886 887 Returns: 888 str: JS8Call configured callsign 889 ''' 890 callsign = callsign.upper() 891 892 if len(callsign) <= 9 and any(char.isdigit() for char in callsign): 893 return self._client.config.set('Configuration', 'MyCall', callsign) 894 else: 895 raise ValueError('callsign must be <= 9 characters in length and contain at least 1 number') 896 897 def get_idle_timeout(self): 898 '''Get JS8Call idle timeout. 899 900 Returns: 901 int: Idle timeout in minutes 902 ''' 903 return self._client.config.get('Configuration', 'TxIdleWatchdog', value_type=int) 904 905 def set_idle_timeout(self, timeout): 906 '''Set JS8Call idle timeout. 907 908 If the JS8Call idle timeout is between 1 and 5 minutes, JS8Call will force the idle timeout to 5 minutes on the next application start or exit. 909 910 The maximum idle timeout is 1440 minutes (24 hours). 911 912 Disable the idle timeout by setting it to 0 (zero). 913 914 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 915 916 Args: 917 timeout (int): Idle timeout in minutes 918 919 Returns: 920 int: Current idle timeout in minutes 921 922 Raises: 923 ValueError: Idle timeout must be between 0 and 1440 minutes 924 ''' 925 if timeout < 0 or timeout > 1440: 926 raise ValueError('Idle timeout must be between 0 and 1440 minutes') 927 928 self._client.config.set('Configuration', 'TxIdleWatchdog', timeout) 929 return self.get_idle_timeout() 930 931 def get_distance_units_miles(self): 932 '''Get JS8Call distance unit setting. 933 934 Returns: 935 bool: True if distance units are set to miles, False if km 936 ''' 937 return self._client.config.get('Configuration', 'Miles', bool) 938 939 def set_distance_units_miles(self, units_miles): 940 '''Set JS8Call distance unit setting. 941 942 Args: 943 units_miles (bool): Set units to miles if True, set to km if False 944 945 Returns: 946 bool: True if distance units are set to miles, False if km 947 ''' 948 self._client.config.set('Configuration', 'Miles', str(units_miles).lower()) 949 return self.get_distance_units_miles() 950 951 def get_distance_units(self): 952 '''Get JS8Call distance units. 953 954 Returns: 955 str: Configured distance units: 'mi' or 'km' 956 ''' 957 if self.get_distance_units_miles(): 958 return 'mi' 959 else: 960 return 'km' 961 962 def set_distance_units(self, units): 963 ''' Set JS8Call distance units. 964 965 Args: 966 units (str): Distance units: 'mi', 'miles', 'km', or 'kilometers' 967 968 Returns: 969 str: Configured distance units: 'miles' or 'km' 970 ''' 971 if units.lower() in ['mi', 'miles']: 972 self.set_distance_units_miles(True) 973 return self.get_distance_units() 974 elif units.lower() in ['km', 'kilometers']: 975 self.set_distance_units_miles(False) 976 return self.get_distance_units() 977 else: 978 raise ValueError('Distance units must be: mi, miles, km, or kilometers') 979 980 def get_station_grid(self, update=False): 981 '''Get JS8Call grid square. 982 983 Args: 984 update (bool): Update if True or use local state if False, defaults to False 985 986 Returns: 987 str: JS8Call configured grid square 988 ''' 989 grid = self._client.js8call.get_state('grid') 990 991 if update or grid is None: 992 msg = Message() 993 msg.type = Message.STATION_GET_GRID 994 self._client.js8call.send(msg) 995 grid = self._client.js8call.watch('grid') 996 997 return grid 998 999 def set_station_grid(self, grid): 1000 '''Set JS8Call grid square. 1001 1002 Args: 1003 grid (str): Grid square 1004 1005 Returns: 1006 str: JS8Call configured grid square 1007 ''' 1008 grid = grid.upper() 1009 msg = Message() 1010 msg.type = Message.STATION_SET_GRID 1011 msg.value = grid 1012 self._client.js8call.send(msg) 1013 time.sleep(self._client._set_get_delay) 1014 return self.get_station_grid(update = True) 1015 1016 def get_station_info(self, update=False): 1017 '''Get JS8Call station information. 1018 1019 Args: 1020 update (bool): Update if True or use local state if False, defaults to False 1021 1022 Returns: 1023 str: JS8Call configured station information 1024 ''' 1025 info = self._client.js8call.get_state('info') 1026 1027 if update or info is None: 1028 msg = Message() 1029 msg.type = Message.STATION_GET_INFO 1030 self._client.js8call.send(msg) 1031 info = self._client.js8call.watch('info') 1032 1033 return info 1034 1035 def set_station_info(self, info): 1036 '''Set JS8Call station information. 1037 1038 *info* updated via API (if connected) and set in JS8Call configuration file. 1039 1040 Args: 1041 info (str): Station information 1042 1043 Returns: 1044 str: JS8Call configured station information 1045 ''' 1046 if self._client.online: 1047 msg = Message() 1048 msg.type = Message.STATION_SET_INFO 1049 msg.value = info 1050 self._client.js8call.send(msg) 1051 time.sleep(self._client._set_get_delay) 1052 info = self.get_station_info(update = True) 1053 1054 # save to config file to preserve over restart 1055 self._client.config.set('Configuration', 'MyInfo', info) 1056 return info 1057 1058 def append_pyjs8call_to_station_info(self): 1059 '''Append pyjs8call info to station info 1060 1061 A string like ', PYJS8CALL V0.0.0' is appended to the current station info. 1062 Example: 'QRPLABS QDX, 40M DIPOLE 33FT, PYJS8CALL V0.2.2' 1063 1064 If a string like ', PYJS8CALL' or ',PYJS8CALL' is found in the current station info, that substring (and everything after it) is dropped before appending the new pyjs8call info. 1065 1066 Returns: 1067 str: JS8Call configured station information 1068 ''' 1069 info = self.get_station_info().upper() 1070 1071 if ', PYJS8CALL' in info: 1072 info = info.split(', PYJS8CALL')[0] 1073 elif ',PYJS8CALL' in info: 1074 info = info.split(',PYJS8CALL')[0] 1075 1076 info = '{}, PYJS8CALL {}'.format(info, pyjs8call.__version__) 1077 return self.set_station_info(info) 1078 1079 def get_bandwidth(self, speed=None): 1080 '''Get JS8Call signal bandwidth based on modem speed. 1081 1082 Uses JS8Call configured speed if no speed is given. 1083 1084 | Speed | Bandwidth | 1085 | -------- | -------- | 1086 | slow | 25 Hz | 1087 | normal | 50 Hz | 1088 | fast | 80 Hz | 1089 | turbo | 160 Hz | 1090 | ultra | 250 Hz | 1091 1092 Args: 1093 speed (str): Speed setting, defaults to None 1094 1095 Returns: 1096 int: Bandwidth of JS8Call signal 1097 ''' 1098 if speed is None: 1099 speed = self.get_speed() 1100 elif isinstance(speed, int): 1101 speed = self.submode_to_speed(speed) 1102 1103 bandwidths = {'slow':25, 'normal':50, 'fast':80, 'turbo':160, 'ultra':250} 1104 1105 if speed in bandwidths: 1106 return bandwidths[speed] 1107 else: 1108 raise ValueError('Invalid speed \'' + speed + '\'') 1109 1110 def get_window_duration(self, speed=None): 1111 '''Get JS8Call rx/tx window duration based on modem speed. 1112 1113 Uses JS8Call configured speed if no speed is given. 1114 1115 | Speed | Duration | 1116 | -------- | -------- | 1117 | slow | 30 seconds | 1118 | normal | 15 seconds | 1119 | fast | 10 seconds | 1120 | turbo | 6 seconds | 1121 | ultra | 4 seconds | 1122 1123 Args: 1124 speed (str): Speed setting, defaults to None 1125 1126 Returns: 1127 int: Duration of JS8Call rx/tx window in seconds 1128 ''' 1129 if speed is None: 1130 speed = self.get_speed() 1131 elif isinstance(speed, int): 1132 speed = self.submode_to_speed(speed) 1133 1134 duration = {'slow': 30, 'normal': 15, 'fast': 10, 'turbo': 6, 'ultra':4} 1135 return duration[speed] 1136 1137 def enable_daily_restart(self, restart_time='02:00'): 1138 '''Enable daily JS8Call restart at specified time. 1139 1140 The intended use of this function is to allow the removal of the *timer.out* file, which grows in size until it consumes all available disk space. This file cannot be removed while the application is running, but is automatically removed during the pyjs8call restart process. 1141 1142 This function adds a schedule entry. See *pyjs8call.schedulemonitor* for more information. 1143 1144 Args: 1145 restart_time (str): Local restart time in 24-hour format (ex. '23:30'), defaults to '02:00' 1146 ''' 1147 # add schedule entry to restart application daily with no settings changes 1148 self._daily_restart_schedule = self._client.schedule.add(restart_time, restart=True) 1149 1150 def disable_daily_restart(self): 1151 '''Disable daily JS8Call restart. 1152 1153 This function removes the schedule entry created by *enable_daily_restart*. See *pyjs8call.schedulemonitor* for more information. 1154 ''' 1155 if self._daily_restart_schedule is None: 1156 return 1157 1158 self._client.schedule.remove(self._daily_restart_schedule.dict()['time'], schedule=self._daily_restart_schedule) 1159 self._daily_restart_schedule = None 1160 1161 def daily_restart_enabled(self): 1162 '''Whether daily JS8Call restart is enabled. 1163 1164 Returns: 1165 bool: True if associated schedule entry is set, False otherwise 1166 ''' 1167 if self._daily_restart_schedule is not None: 1168 return True 1169 else: 1170 return False 1171 1172 def get_daily_restart_time(self): 1173 '''Get daily JS8Call restart time. 1174 1175 Returns: 1176 str or None: Local restart time in 24-hour format (ex. '23:30'), or None if not enabled 1177 ''' 1178 if self._daily_restart_schedule is None: 1179 return 1180 1181 return self._daily_restart_schedule.start.strftime('%H:%M')
Settings function container.
This class is initilized by pyjs8call.client.Client.
46 def __init__(self, client): 47 '''Initialize settings object. 48 49 Returns: 50 pyjs8call.client.Settings: Constructed setting object 51 ''' 52 self._client = client 53 self._daily_restart_schedule = None 54 self.loaded_settings = None 55 '''Loaded settings container (see python3 configparser for more information)''' 56 57 self._settings_map = { 58 'station' : { 59 'callsign': lambda value: self.set_station_callsign(value), 60 'grid': lambda value: self.set_station_grid(value), 61 'speed': lambda value: self.set_speed(value), 62 'freq': lambda value: self.set_freq(value), 63 'frequency': lambda value: self.set_freq(value), 64 'offset': lambda value: self.set_offset(value), 65 'info': lambda value: self.set_station_info(value), 66 'append_pyjs8call_info': lambda value: self.append_pyjs8call_to_station_info(), 67 'daily_restart': lambda value: self.enable_daily_restart(value) if value else self.disable_daily_restart() 68 }, 69 'general': { 70 'groups': lambda value: self.set_groups(value), 71 'multi_speed_decode': lambda value: self.enable_multi_decode() if value else self.disable_multi_decode(), 72 'autoreply_on_at_startup': lambda value: self.enable_autoreply_startup() if value else self.disable_autoreply_startup(), 73 'autoreply_confirmation': lambda value: self.enable_autoreply_confirmation() if value else self.disable_autoreply_confirmation(), 74 'allcall': lambda value: self.enable_allcall() if value else self.disable_allcall(), 75 'reporting': lambda value: self.enable_reporting() if value else self.disable_reporting(), 76 'transmit': lambda value: self.enable_transmit() if value else self.disable_transmit(), 77 'idle_timeout': lambda value: self.set_idle_timeout(value), 78 'distance_units': lambda value: self.set_distance_units(value) 79 }, 80 'heartbeat': { 81 'enable': lambda value: self._client.heartbeat.enable() if value else self._client.heartbeat.disable(), 82 'interval': lambda value: self.set_heartbeat_interval(value), 83 'acknowledgements': lambda value: self.enable_heartbeat_acknowledgements() if value else self.disable_heartbeat_acknowledgements(), 84 'pause_during_qso': lambda value: self.pause_heartbeat_during_qso() if value else self.allow_heartbeat_during_qso() 85 }, 86 'profile': { 87 'profile': lambda value: self.set_profile(value), 88 'set_profile_on_exit': lambda value: self._client.set_profile_on_exit(value) 89 }, 90 'highlight': { 91 'primary_words': lambda value: self.set_primary_highlight_words(value), 92 'secondary_words': lambda value: self.set_secondary_highlight_words(value) 93 }, 94 'spots': { 95 'watch_stations': lambda value: self._client.spots.set_watched_stations(value), 96 'watch_groups': lambda value: self._client.spots.set_watched_groups(value) 97 }, 98 'notifications': { 99 'enable': lambda value: self._client.notifications.enable() if value else self._client.notifications.disable(), 100 'smtp_server': lambda value: self._client.notifications.set_smtp_server(value), 101 'smtp_port': lambda value: self._client.notifications.set_smtp_server_port(value), 102 'smtp_email_address': lambda value: self._client.notifications.set_smtp_email_address(value), 103 'smtp_password': lambda value: self._client.notifications.set_smtp_password(value), 104 'notification_email_address': lambda value: self._client.notifications.set_email_destination(value), 105 'notification_email_subject': lambda value: self._client.notifications.set_email_subject(value), 106 'incoming': lambda value: self._client.notifications.enable_incoming() if value else self._client.notifications.disable_incoming(), 107 'spots': lambda value: self._client.notifications.enable_spots() if value else self._client.notifications.disable_spots(), 108 'station_spots': lambda value: self._client.notifications.enable_station_spots() if value else self._client.notifications.disable_station_spots(), 109 'group_spots': lambda value: self._client.notifications.enable_group_spots() if value else self._client.notifications.disable_group_spots() 110 } 111 } 112 113 # settings set via js8call config file 114 self._pre_start_settings = { 115 'station' : [ 116 'callsign', 117 'speed' 118 ], 119 'general': [ 120 'groups', 121 'multi_speed_decode', 122 'autoreplay_on_at_startup', 123 'autoreply_confirmation', 124 'allcall', 125 'reporting', 126 'transmit', 127 'idle_timeout', 128 'distance_units' 129 ], 130 'heartbeat': [ 131 'interval', 132 'acknowledgements', 133 'pause_during_qso' 134 ], 135 'profile': [ 136 'profile', 137 'set_profile_on_exit' 138 ], 139 'highlight': [ 140 'primary_words', 141 'secondary_words' 142 ], 143 'spots': [ 144 ], 145 'notifications': [ 146 ] 147 }
Initialize settings object.
Returns:
pyjs8call.client.Settings: Constructed setting object
149 def load(self, settings_path): 150 '''Load pyjs8call settings from file. 151 152 The settings file referenced here is specific to pyjs8call, and is not the same as the JS8Call configuration file. The pyjs8call settings file is not required. 153 154 This function must be called before calling *client.start()*. Settings that must be set before or after starting the JS8Call application are handled automatically. Settings that affect the JS8Call config file are set immediately. All other settings are set after *client.start()* is called. 155 156 Example settings file: 157 158 ``` 159 [station] 160 161 callsign=CALL0SIGN 162 grid=EM19 163 speed=normal 164 freq=7078000 165 offset=1750 166 info=QDX 5W, DIPOLE 30FT 167 append_pyjs8call_info=true 168 daily_restart=02:00 169 170 [general] 171 172 groups=@TTP, @AMRRON 173 multi_speed_decode=true 174 autoreply_on_at_startup=true 175 autoreply_confirmation=false 176 allcall=true 177 reporting=true 178 transmit=true 179 idle_timeout=0 180 distance_units=miles 181 182 [heartbeat] 183 184 enable=true 185 interval=15 186 acknowledgements=true 187 pause_during_qso=true 188 189 [profile] 190 191 profile=Default 192 set_profile_on_exit=Default 193 194 [highlight] 195 196 primary_words=KT7RUN, OH8STN 197 secondary_words=simplyequipped 198 199 [spots] 200 201 watch_stations=KT7RUN, OH8STN 202 watch_groups=@TTP, @AMRRON 203 204 [notifications] 205 206 enable=true 207 smtp_server=smtp.gmail.com 208 smtp_port=465 209 smtp_email_address=email@address.com 210 smtp_password=APP_PASSWORD 211 notification_email_address=0123456789@vtext.com 212 notification_email_subject= 213 incoming=true 214 spots=false 215 station_spots=true 216 group_spots=true 217 ``` 218 219 Args: 220 settings_path (str): Relative or absolute path to settings file 221 222 Raises: 223 OSError: Specified settings file not found 224 ''' 225 settings_path = os.path.expanduser(settings_path) 226 settings_path = os.path.abspath(settings_path) 227 228 if not os.path.exists(settings_path): 229 raise FileNotFoundError('Specified settings file not found: {}'.format(settings_path)) 230 231 self.loaded_settings = configparser.ConfigParser(interpolation = None) 232 self.loaded_settings.read(settings_path) 233 234 self.apply_loaded_settings()
Load pyjs8call settings from file.
The settings file referenced here is specific to pyjs8call, and is not the same as the JS8Call configuration file. The pyjs8call settings file is not required.
This function must be called before calling client.start(). Settings that must be set before or after starting the JS8Call application are handled automatically. Settings that affect the JS8Call config file are set immediately. All other settings are set after client.start() is called.
Example settings file:
[station]
callsign=CALL0SIGN
grid=EM19
speed=normal
freq=7078000
offset=1750
info=QDX 5W, DIPOLE 30FT
append_pyjs8call_info=true
daily_restart=02:00
[general]
groups=@TTP, @AMRRON
multi_speed_decode=true
autoreply_on_at_startup=true
autoreply_confirmation=false
allcall=true
reporting=true
transmit=true
idle_timeout=0
distance_units=miles
[heartbeat]
enable=true
interval=15
acknowledgements=true
pause_during_qso=true
[profile]
profile=Default
set_profile_on_exit=Default
[highlight]
primary_words=KT7RUN, OH8STN
secondary_words=simplyequipped
[spots]
watch_stations=KT7RUN, OH8STN
watch_groups=@TTP, @AMRRON
[notifications]
enable=true
smtp_server=smtp.gmail.com
smtp_port=465
smtp_email_address=email@address.com
smtp_password=APP_PASSWORD
notification_email_address=0123456789@vtext.com
notification_email_subject=
incoming=true
spots=false
station_spots=true
group_spots=true
Arguments:
- settings_path (str): Relative or absolute path to settings file
Raises:
- OSError: Specified settings file not found
236 def apply_loaded_settings(self, post_start=False): 237 '''Apply loaded pyjs8call settings. 238 239 This function is called internally by *load_settings()* and *client.start()*. 240 241 Args: 242 post_start (bool): Post start processing if True, pre start processing if False, defaults to False 243 ''' 244 for section in self.loaded_settings.sections(): 245 #skip unsupported section 246 if section not in self._settings_map: 247 continue 248 249 for key, value in self.loaded_settings[section].items(): 250 # skip unsupported key 251 if key not in self._settings_map[section]: 252 continue 253 254 # skip post start settings during pre start processing 255 if not post_start and key in self._pre_start_settings[section]: 256 value = self._parse_loaded_value(value) 257 self._settings_map[section][key](value) 258 259 # skip pre start settings during post start processing 260 if post_start and not key in self._pre_start_settings[section]: 261 value = self._parse_loaded_value(value) 262 self._settings_map[section][key](value)
Apply loaded pyjs8call settings.
This function is called internally by load_settings() and client.start().
Arguments:
- post_start (bool): Post start processing if True, pre start processing if False, defaults to False
283 def enable_heartbeat_networking(self): 284 '''Enable heartbeat networking via config file. 285 286 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 287 288 Note that this function disables JS8Call application heartbeat networking via the config file. To enable the pyjs8call heartbeat network messaging module see *client.heartbeat.enable()*. 289 ''' 290 self._client.config.set('Common', 'SubModeHB', 'true')
Enable heartbeat networking via config file.
It is recommended that this function be called before calling client.start(). If this function is called after client.start() then the application will have to be restarted to utilize the new config file settings. See client.restart().
Note that this function disables JS8Call application heartbeat networking via the config file. To enable the pyjs8call heartbeat network messaging module see client.heartbeat.enable().
292 def disable_heartbeat_networking(self): 293 '''Disable heartbeat networking via config file. 294 295 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 296 297 Note that this function disables JS8Call application heartbeat networking via the config file. To disable the pyjs8call heartbeat network messaging module see *client.heartbeat.disable()*. 298 ''' 299 self._client.config.set('Common', 'SubModeHB', 'false')
Disable heartbeat networking via config file.
It is recommended that this function be called before calling client.start(). If this function is called after client.start() then the application will have to be restarted to utilize the new config file settings. See client.restart().
Note that this function disables JS8Call application heartbeat networking via the config file. To disable the pyjs8call heartbeat network messaging module see client.heartbeat.disable().
301 def heartbeat_networking_enabled(self): 302 '''Whether heartbeat networking enabled in config file. 303 304 Returns: 305 bool: True if heartbeat networking enabled, False otherwise 306 ''' 307 return self._client.config.get('Common', 'SubModeHB', bool)
Whether heartbeat networking enabled in config file.
Returns:
bool: True if heartbeat networking enabled, False otherwise
309 def get_heartbeat_interval(self): 310 '''Get heartbeat networking interval. 311 312 Returns: 313 int: Heartbeat networking time interval in minutes 314 ''' 315 return self._client.config.get('Common', 'HBInterval', int)
Get heartbeat networking interval.
Returns:
int: Heartbeat networking time interval in minutes
317 def set_heartbeat_interval(self, interval): 318 '''Set the heartbeat networking interval. 319 320 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 321 322 Args: 323 interval (int): New heartbeat networking time interval in minutes 324 325 Returns: 326 int: Current heartbeat networking time interval in minutes 327 ''' 328 return self._client.config.set('Common', 'HBInterval', interval)
Set the heartbeat networking interval.
It is recommended that this function be called before calling client.start(). If this function is called after client.start() then the application will have to be restarted to utilize the new config file settings. See client.restart().
Arguments:
- interval (int): New heartbeat networking time interval in minutes
Returns:
int: Current heartbeat networking time interval in minutes
330 def enable_heartbeat_acknowledgements(self): 331 '''Enable heartbeat acknowledgements via config file. 332 333 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 334 335 Also enables JS8Call heartbeat networking, since heartbeat acknowledgements will not be enabled without heartbeat networking enabled first. This only enables the feature within JS8Call, and does not casue heartbeats to be sent. 336 ''' 337 self.enable_heartbeat_networking() 338 self._client.config.set('Common', 'SubModeHBAck', 'true')
Enable heartbeat acknowledgements via config file.
It is recommended that this function be called before calling client.start(). If this function is called after client.start() then the application will have to be restarted to utilize the new config file settings. See client.restart().
Also enables JS8Call heartbeat networking, since heartbeat acknowledgements will not be enabled without heartbeat networking enabled first. This only enables the feature within JS8Call, and does not casue heartbeats to be sent.
340 def disable_heartbeat_acknowledgements(self): 341 '''Disable heartbeat acknowledgements via config file. 342 343 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 344 ''' 345 self._client.config.set('Common', 'SubModeHBAck', 'false')
Disable heartbeat acknowledgements via config file.
It is recommended that this function be called before calling client.start(). If this function is called after client.start() then the application will have to be restarted to utilize the new config file settings. See client.restart().
347 def heartbeat_acknowledgements_enabled(self): 348 '''Whether heartbeat acknowledgements enabled in config file. 349 350 Returns: 351 bool: True if heartbeat acknowledgements enabled, False otherwise 352 ''' 353 return self._client.config.get('Common', 'SubModeHBAck', bool)
Whether heartbeat acknowledgements enabled in config file.
Returns:
bool: True if heartbeat acknowledgements enabled, False otherwise
355 def pause_heartbeat_during_qso(self): 356 '''Pause heartbeat messages during QSO via config file. 357 358 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 359 ''' 360 self._client.config.set('Configuration', 'HeartbeatQSOPause', 'true')
Pause heartbeat messages during QSO via config file.
It is recommended that this function be called before calling client.start(). If this function is called after client.start() then the application will have to be restarted to utilize the new config file settings. See client.restart().
362 def allow_heartbeat_during_qso(self): 363 '''Allow heartbeat messages during QSO via config file. 364 365 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 366 ''' 367 self._client.config.set('Configuration', 'HeartbeatQSOPause', 'false')
Allow heartbeat messages during QSO via config file.
It is recommended that this function be called before calling client.start(). If this function is called after client.start() then the application will have to be restarted to utilize the new config file settings. See client.restart().
369 def heartbeat_during_qso_paused(self): 370 '''Whether heartbeat messages paused during QSO in config file. 371 372 Returns: 373 bool: True if heartbeat messages paused during QSO, False otherwise 374 ''' 375 return self._client.config.get('Configuration', 'HeartbeatQSOPause', bool)
Whether heartbeat messages paused during QSO in config file.
Returns:
bool: True if heartbeat messages paused during QSO, False otherwise
377 def enable_multi_decode(self): 378 '''Enable multi-speed decoding via config file. 379 380 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 381 ''' 382 self._client.config.set('Common', 'SubModeHBMultiDecode', 'true')
Enable multi-speed decoding via config file.
It is recommended that this function be called before calling client.start(). If this function is called after client.start() then the application will have to be restarted to utilize the new config file settings. See client.restart().
384 def disable_multi_decode(self): 385 '''Disable multi-speed decoding via config file. 386 387 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 388 ''' 389 self._client.config.set('Common', 'SubModeMultiDecode', 'false')
Disable multi-speed decoding via config file.
It is recommended that this function be called before calling client.start(). If this function is called after client.start() then the application will have to be restarted to utilize the new config file settings. See client.restart().
391 def multi_decode_enabled(self): 392 '''Whether multi-decode enabled in config file. 393 394 Returns: 395 bool: True if multi-decode enabled, False otherwise 396 ''' 397 return self._client.config.get('Common', 'SubModeMultiDecode', bool)
Whether multi-decode enabled in config file.
Returns:
bool: True if multi-decode enabled, False otherwise
399 def enable_autoreply_startup(self): 400 '''Enable autoreply on start-up via config file. 401 402 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 403 ''' 404 self._client.config.set('Configuration', 'AutoreplyOnAtStartup', 'true')
Enable autoreply on start-up via config file.
It is recommended that this function be called before calling client.start(). If this function is called after client.start() then the application will have to be restarted to utilize the new config file settings. See client.restart().
406 def disable_autoreply_startup(self): 407 '''Disable autoreply on start-up via config file. 408 409 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 410 ''' 411 self._client.config.set('Configuration', 'AutoreplyOnAtStartup', 'false')
Disable autoreply on start-up via config file.
It is recommended that this function be called before calling client.start(). If this function is called after client.start() then the application will have to be restarted to utilize the new config file settings. See client.restart().
413 def autoreply_startup_enabled(self): 414 '''Whether autoreply enabled at start-up in config file. 415 416 Returns: 417 bool: True if autoreply is enabled at start-up, False otherwise 418 ''' 419 return self._client.config.get('Configuration', 'AutoreplyOnAtStartup', bool)
Whether autoreply enabled at start-up in config file.
Returns:
bool: True if autoreply is enabled at start-up, False otherwise
421 def enable_autoreply_confirmation(self): 422 '''Enable autoreply confirmation via config file. 423 424 When running headless the autoreply confirmation dialog box will be inaccessible. 425 426 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 427 ''' 428 self._client.config.set('Configuration', 'AutoreplyConfirmation', 'true')
Enable autoreply confirmation via config file.
When running headless the autoreply confirmation dialog box will be inaccessible.
It is recommended that this function be called before calling client.start(). If this function is called after client.start() then the application will have to be restarted to utilize the new config file settings. See client.restart().
430 def disable_autoreply_confirmation(self): 431 '''Disable autoreply confirmation via config file. 432 433 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 434 ''' 435 self._client.config.set('Configuration', 'AutoreplyConfirmation', 'false')
Disable autoreply confirmation via config file.
It is recommended that this function be called before calling client.start(). If this function is called after client.start() then the application will have to be restarted to utilize the new config file settings. See client.restart().
437 def autoreply_confirmation_enabled(self): 438 '''Whether autoreply confirmation enabled in config file. 439 440 Returns: 441 bool: True if autoreply confirmation enabled, False otherwise 442 ''' 443 return self._client.config.get('Configuration', 'AutoreplyConfirmation', bool)
Whether autoreply confirmation enabled in config file.
Returns:
bool: True if autoreply confirmation enabled, False otherwise
445 def enable_allcall(self): 446 '''Enable @ALLCALL participation via config file. 447 448 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 449 ''' 450 self._client.config.set('Configuration', 'AvoidAllcall', 'false')
Enable @ALLCALL participation via config file.
It is recommended that this function be called before calling client.start(). If this function is called after client.start() then the application will have to be restarted to utilize the new config file settings. See client.restart().
452 def disable_allcall(self): 453 '''Disable @ALLCALL participation via config file. 454 455 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 456 ''' 457 self._client.config.set('Configuration', 'AvoidAllcall', 'true')
Disable @ALLCALL participation via config file.
It is recommended that this function be called before calling client.start(). If this function is called after client.start() then the application will have to be restarted to utilize the new config file settings. See client.restart().
459 def allcall_enabled(self): 460 '''Whether @ALLCALL participation enabled in config file. 461 462 Returns: 463 bool: True if @ALLCALL participation enabled, False otherwise 464 ''' 465 return not self._client.config.get('Configuration', 'AvoidAllcall', bool)
Whether @ALLCALL participation enabled in config file.
Returns:
bool: True if @ALLCALL participation enabled, False otherwise
467 def enable_reporting(self): 468 '''Enable PSKReporter reporting via config file. 469 470 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 471 ''' 472 self._client.config.set('Configuration', 'PSKReporter', 'true')
Enable PSKReporter reporting via config file.
It is recommended that this function be called before calling client.start(). If this function is called after client.start() then the application will have to be restarted to utilize the new config file settings. See client.restart().
474 def disable_reporting(self): 475 '''Disable PSKReporter reporting via config file. 476 477 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 478 ''' 479 self._client.config.set('Configuration', 'PSKReporter', 'false')
Disable PSKReporter reporting via config file.
It is recommended that this function be called before calling client.start(). If this function is called after client.start() then the application will have to be restarted to utilize the new config file settings. See client.restart().
481 def reporting_enabled(self): 482 '''Whether PSKReporter reporting enabled in config file. 483 484 Returns: 485 bool: True if reporting enabled, False otherwise 486 ''' 487 return self._client.config.get('Configuration', 'PSKReporter', bool)
Whether PSKReporter reporting enabled in config file.
Returns:
bool: True if reporting enabled, False otherwise
489 def enable_transmit(self): 490 '''Enable JS8Call transmitting via config file. 491 492 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 493 ''' 494 self._client.config.set('Configuration', 'TransmitOFF', 'false')
Enable JS8Call transmitting via config file.
It is recommended that this function be called before calling client.start(). If this function is called after client.start() then the application will have to be restarted to utilize the new config file settings. See client.restart().
496 def disable_transmit(self): 497 '''Disable JS8Call transmitting via config file. 498 499 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 500 ''' 501 self._client.config.set('Configuration', 'TransmitOFF', 'true')
Disable JS8Call transmitting via config file.
It is recommended that this function be called before calling client.start(). If this function is called after client.start() then the application will have to be restarted to utilize the new config file settings. See client.restart().
503 def transmit_enabled(self): 504 '''Whether JS8Call transmitting enabled in config file. 505 506 Returns: 507 bool: True if transmitting enabled, False otherwise 508 ''' 509 return not self._client.config.get('Configuration', 'TransmitOFF', bool)
Whether JS8Call transmitting enabled in config file.
Returns:
bool: True if transmitting enabled, False otherwise
511 def get_profile(self): 512 '''Get active JS8call configuration profile via config file. 513 514 This is a convenience function. See pyjs8call.confighandler for other configuration related functions. 515 516 Returns: 517 str: Name of the active configuration profile 518 ''' 519 return self._client.config.get_active_profile()
Get active JS8call configuration profile via config file.
This is a convenience function. See pyjs8call.confighandler for other configuration related functions.
Returns:
str: Name of the active configuration profile
521 def get_profile_list(self): 522 '''Get list of JS8Call configuration profiles via config file. 523 524 This is a convenience function. See pyjs8call.confighandler for other configuration related functions. 525 526 Returns: 527 list: List of configuration profile names 528 ''' 529 return self._client.config.get_profile_list()
Get list of JS8Call configuration profiles via config file.
This is a convenience function. See pyjs8call.confighandler for other configuration related functions.
Returns:
list: List of configuration profile names
531 def set_profile(self, profile, restore_on_exit=False, create=False): 532 '''Set active JS8Call configuration profile via config file. 533 534 This is a convenience function. See pyjs8call.confighandler for other configuration related functions. 535 536 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 537 538 Args: 539 profile (str): Profile name 540 restore_on_exit (bool): Restore previous profile on exit, defaults to False 541 create (bool): Create a new profile (copying from Default) if the specified profile does not exist, defaults to False 542 543 Raises: 544 ValueError: Specified profile name does not exist 545 ''' 546 if profile not in self.get_profile_list(): 547 if create: 548 # copy from Default profile 549 self.create_new_profile(profile) 550 else: 551 raise ValueError('Config profile \'' + profile + '\' does not exist') 552 553 if restore_on_exit: 554 self._client._previous_profile = self.get_profile() 555 556 # set profile as active 557 self._client.config.change_profile(profile)
Set active JS8Call configuration profile via config file.
This is a convenience function. See pyjs8call.confighandler for other configuration related functions.
It is recommended that this function be called before calling client.start(). If this function is called after client.start() then the application will have to be restarted to utilize the new config file settings. See client.restart().
Arguments:
- profile (str): Profile name
- restore_on_exit (bool): Restore previous profile on exit, defaults to False
- create (bool): Create a new profile (copying from Default) if the specified profile does not exist, defaults to False
Raises:
- ValueError: Specified profile name does not exist
559 def create_new_profile(self, new_profile, copy_profile='Default'): 560 '''Create new JS8Call configuration profile. 561 562 This is a convenience function. See pyjs8call.confighandler for other configuration related functions. 563 564 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 565 566 Args: 567 new_profile (str): Name of new profile to create 568 copy_profile (str): Name of an existing profile to copy when creating the new profile, defaults to 'Default' 569 ''' 570 self._client.config.create_new_profile(new_profile, copy_profile)
Create new JS8Call configuration profile.
This is a convenience function. See pyjs8call.confighandler for other configuration related functions.
It is recommended that this function be called before calling client.start(). If this function is called after client.start() then the application will have to be restarted to utilize the new config file settings. See client.restart().
Arguments:
- new_profile (str): Name of new profile to create
- copy_profile (str): Name of an existing profile to copy when creating the new profile, defaults to 'Default'
572 def get_groups_list(self): 573 '''Get list of configured JS8Call groups via config file. 574 575 This is a convenience function. See pyjs8call.confighandler for other configuration related functions. 576 577 Returns: 578 list: List of configured group names 579 ''' 580 return self._client.config.get_groups()
Get list of configured JS8Call groups via config file.
This is a convenience function. See pyjs8call.confighandler for other configuration related functions.
Returns:
list: List of configured group names
582 def add_group(self, group): 583 '''Add configured JS8Call group via config file. 584 585 This is a convenience function. See pyjs8call.confighandler for other configuration related functions. 586 587 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 588 589 Args: 590 group (str): Group name 591 ''' 592 self._client.config.add_group(group)
Add configured JS8Call group via config file.
This is a convenience function. See pyjs8call.confighandler for other configuration related functions.
It is recommended that this function be called before calling client.start(). If this function is called after client.start() then the application will have to be restarted to utilize the new config file settings. See client.restart().
Arguments:
- group (str): Group name
594 def remove_group(self, group): 595 '''Remove configured JS8Call group via config file. 596 597 This is a convenience function. See pyjs8call.confighandler for other configuration related functions. 598 599 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 600 601 Args: 602 group (str): Group name 603 ''' 604 self._client.config.remove_group(group)
Remove configured JS8Call group via config file.
This is a convenience function. See pyjs8call.confighandler for other configuration related functions.
It is recommended that this function be called before calling client.start(). If this function is called after client.start() then the application will have to be restarted to utilize the new config file settings. See client.restart().
Arguments:
- group (str): Group name
606 def set_groups(self, groups): 607 '''Set configured JS8Call groups via config file. 608 609 This is a convenience function. See pyjs8call.confighandler for other configuration related functions. 610 611 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 612 613 Args: 614 groups (list): List of group names 615 ''' 616 if isinstance(groups, str): 617 groups = groups.split(',') 618 619 groups = ['@' + group.strip(' @') for group in groups] 620 groups = ', '.join(groups) 621 self._client.config.set('Configuration', 'MyGroups', groups)
Set configured JS8Call groups via config file.
This is a convenience function. See pyjs8call.confighandler for other configuration related functions.
It is recommended that this function be called before calling client.start(). If this function is called after client.start() then the application will have to be restarted to utilize the new config file settings. See client.restart().
Arguments:
- groups (list): List of group names
623 def get_primary_highlight_words(self): 624 '''Get primary highlight words via config file. 625 626 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 627 628 Returns: 629 list: Words that should be highlighted on the JC8Call UI 630 ''' 631 words = self._client.config.get('Configuration', 'PrimaryHighlightWords') 632 633 if words == '@Invalid()': 634 words = [] 635 elif words is not None: 636 words = words.split(', ') 637 638 return words
Get primary highlight words via config file.
It is recommended that this function be called before calling client.start(). If this function is called after client.start() then the application will have to be restarted to utilize the new config file settings. See client.restart().
Returns:
list: Words that should be highlighted on the JC8Call UI
640 def set_primary_highlight_words(self, words): 641 '''Set primary highlight words via config file. 642 643 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 644 645 Args: 646 words (list): Words that should be highlighted on the JC8Call UI 647 ''' 648 if isinstance(words, str): 649 words = [word.strip() for word in words.split(',')] 650 651 if len(words) == 0: 652 words = '@Invalid()' 653 else: 654 words = ', '.join(words) 655 656 self._client.config.set('Configuration', 'PrimaryHighlightWords', words)
Set primary highlight words via config file.
It is recommended that this function be called before calling client.start(). If this function is called after client.start() then the application will have to be restarted to utilize the new config file settings. See client.restart().
Arguments:
- words (list): Words that should be highlighted on the JC8Call UI
658 def get_secondary_highlight_words(self): 659 '''Get secondary highlight words via config file. 660 661 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 662 663 Returns: 664 list: Words that should be highlighted on the JC8Call UI 665 ''' 666 words = self._client.config.get('Configuration', 'SecondaryHighlightWords') 667 668 if words == '@Invalid()': 669 words = [] 670 elif words is not None: 671 words = words.split(', ') 672 673 return words
Get secondary highlight words via config file.
It is recommended that this function be called before calling client.start(). If this function is called after client.start() then the application will have to be restarted to utilize the new config file settings. See client.restart().
Returns:
list: Words that should be highlighted on the JC8Call UI
675 def set_secondary_highlight_words(self, words): 676 '''Set secondary highlight words via config file. 677 678 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 679 680 Args: 681 words (list): Words that should be highlighted on the JC8Call UI 682 ''' 683 if isinstance(words, str): 684 words = [word.strip() for word in words.split(',')] 685 686 if len(words) == 0: 687 words = '@Invalid()' 688 else: 689 words = ', '.join(words) 690 691 self._client.config.set('Configuration', 'SecondaryHighlightWords', words)
Set secondary highlight words via config file.
It is recommended that this function be called before calling client.start(). If this function is called after client.start() then the application will have to be restarted to utilize the new config file settings. See client.restart().
Arguments:
- words (list): Words that should be highlighted on the JC8Call UI
693 def submode_to_speed(self, submode): 694 '''Map submode *int* to speed *str*. 695 696 | Submode | Speed | 697 | -------- | -------- | 698 | 0 | normal | 699 | 1 | fast | 700 | 2 | turbo | 701 | 4 | slow | 702 | 8 | ultra | 703 704 Args: 705 submode (int): Submode to map to text 706 707 Returns: 708 str: Speed as text 709 ''' 710 # map integer to text 711 speeds = {4:'slow', 0:'normal', 1:'fast', 2:'turbo', 8:'ultra'} 712 713 if submode is not None and int(submode) in speeds: 714 return speeds[int(submode)] 715 else: 716 raise ValueError('Invalid submode \'' + str(submode) + '\'')
Map submode int to speed str.
Submode | Speed |
---|---|
0 | normal |
1 | fast |
2 | turbo |
4 | slow |
8 | ultra |
Arguments:
- submode (int): Submode to map to text
Returns:
str: Speed as text
718 def get_speed(self, update=False): 719 '''Get JS8Call modem speed. 720 721 Possible modem speeds: 722 - slow 723 - normal 724 - fast 725 - turbo 726 - ultra 727 728 Args: 729 update (bool): Update speed if True or use local state if False, defaults to False 730 731 Returns: 732 str: JS8call modem speed setting 733 ''' 734 speed = self._client.js8call.get_state('speed') 735 736 if update or speed is None: 737 msg = Message() 738 msg.set('type', Message.MODE_GET_SPEED) 739 self._client.js8call.send(msg) 740 speed = self._client.js8call.watch('speed') 741 742 return self.submode_to_speed(speed)
Get JS8Call modem speed.
Possible modem speeds:
- slow
- normal
- fast
- turbo
- ultra
Arguments:
- update (bool): Update speed if True or use local state if False, defaults to False
Returns:
str: JS8call modem speed setting
744 def set_speed(self, speed): 745 '''Set JS8Call modem speed via config file. 746 747 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 748 749 Possible modem speeds: 750 - slow 751 - normal 752 - fast 753 - turbo 754 - ultra 755 756 Args: 757 speed (str): Speed to set 758 759 Returns: 760 str: JS8Call modem speed setting 761 762 ''' 763 if isinstance(speed, str): 764 speeds = {'slow':4, 'normal':0, 'fast':1, 'turbo':2, 'ultra':8} 765 if speed in speeds: 766 speed = speeds[speed] 767 else: 768 raise ValueError('Invalid speed: ' + str(speed)) 769 770 return self._client.config.set('Common', 'SubMode', speed)
Set JS8Call modem speed via config file.
It is recommended that this function be called before calling client.start(). If this function is called after client.start() then the application will have to be restarted to utilize the new config file settings. See client.restart().
Possible modem speeds:
- slow
- normal
- fast
- turbo
- ultra
Arguments:
- speed (str): Speed to set
Returns:
str: JS8Call modem speed setting
780 def get_freq(self, update=False): 781 '''Get JS8Call dial frequency. 782 783 Args: 784 update (bool): Update if True or use local state if False, defaults to False 785 786 Returns: 787 int: Dial frequency in Hz 788 ''' 789 freq = self._client.js8call.get_state('dial') 790 791 if update or freq is None: 792 msg = Message() 793 msg.type = Message.RIG_GET_FREQ 794 self._client.js8call.send(msg) 795 freq = self._client.js8call.watch('dial') 796 797 return freq
Get JS8Call dial frequency.
Arguments:
- update (bool): Update if True or use local state if False, defaults to False
Returns:
int: Dial frequency in Hz
799 def set_freq(self, freq): 800 '''Set JS8Call dial frequency. 801 802 Args: 803 freq (int): Dial frequency in Hz 804 805 Returns: 806 int: Dial frequency in Hz 807 ''' 808 msg = Message() 809 msg.set('type', Message.RIG_SET_FREQ) 810 msg.set('params', {'DIAL': freq, 'OFFSET': self._client.js8call.get_state('offset')}) 811 self._client.js8call.send(msg) 812 time.sleep(self._client._set_get_delay) 813 return self.get_freq(update = True)
Set JS8Call dial frequency.
Arguments:
- freq (int): Dial frequency in Hz
Returns:
int: Dial frequency in Hz
815 def get_band(self): 816 '''Get frequency band designation. 817 818 Returns: 819 str: Band designator like \'40m\' or Client.OOB (out-of-band) 820 ''' 821 return Client.freq_to_band(self.get_freq())
Get frequency band designation.
Returns:
str: Band designator like '40m' or Client.OOB (out-of-band)
823 def get_offset(self, update=False): 824 '''Get JS8Call offset frequency. 825 826 Args: 827 update (bool): Update if True or use local state if False, defaults to False 828 829 Returns: 830 int: Offset frequency in Hz 831 ''' 832 offset = self._client.js8call.get_state('offset') 833 834 if update or offset is None: 835 msg = Message() 836 msg.type = Message.RIG_GET_FREQ 837 self._client.js8call.send(msg) 838 offset = self._client.js8call.watch('offset') 839 840 return offset
Get JS8Call offset frequency.
Arguments:
- update (bool): Update if True or use local state if False, defaults to False
Returns:
int: Offset frequency in Hz
842 def set_offset(self, offset): 843 '''Set JS8Call offset frequency. 844 845 Args: 846 offset (int): Offset frequency in Hz 847 848 Returns: 849 int: Offset frequency in Hz 850 ''' 851 msg = Message() 852 msg.set('type', Message.RIG_SET_FREQ) 853 msg.set('params', {'DIAL': self._client.js8call.get_state('dial'), 'OFFSET': offset}) 854 self._client.js8call.send(msg) 855 time.sleep(self._client._set_get_delay) 856 return self.get_offset(update = True)
Set JS8Call offset frequency.
Arguments:
- offset (int): Offset frequency in Hz
Returns:
int: Offset frequency in Hz
858 def get_station_callsign(self, update=False): 859 '''Get JS8Call callsign. 860 861 Args: 862 update (bool): Update if True or use local state if False, defaults to False 863 864 Returns: 865 str: JS8Call configured callsign 866 ''' 867 callsign = self._client.js8call.get_state('callsign') 868 869 if update or callsign is None: 870 msg = Message() 871 msg.type = Message.STATION_GET_CALLSIGN 872 self._client.js8call.send(msg) 873 callsign = self._client.js8call.watch('callsign') 874 875 return callsign
Get JS8Call callsign.
Arguments:
- update (bool): Update if True or use local state if False, defaults to False
Returns:
str: JS8Call configured callsign
877 def set_station_callsign(self, callsign): 878 '''Set JS8Call callsign. 879 880 Callsign must be a maximum of 9 characters and contain at least one number. 881 882 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 883 884 Args: 885 callsign (str): Callsign to set 886 887 Returns: 888 str: JS8Call configured callsign 889 ''' 890 callsign = callsign.upper() 891 892 if len(callsign) <= 9 and any(char.isdigit() for char in callsign): 893 return self._client.config.set('Configuration', 'MyCall', callsign) 894 else: 895 raise ValueError('callsign must be <= 9 characters in length and contain at least 1 number')
Set JS8Call callsign.
Callsign must be a maximum of 9 characters and contain at least one number.
It is recommended that this function be called before calling client.start(). If this function is called after client.start() then the application will have to be restarted to utilize the new config file settings. See client.restart().
Arguments:
- callsign (str): Callsign to set
Returns:
str: JS8Call configured callsign
897 def get_idle_timeout(self): 898 '''Get JS8Call idle timeout. 899 900 Returns: 901 int: Idle timeout in minutes 902 ''' 903 return self._client.config.get('Configuration', 'TxIdleWatchdog', value_type=int)
Get JS8Call idle timeout.
Returns:
int: Idle timeout in minutes
905 def set_idle_timeout(self, timeout): 906 '''Set JS8Call idle timeout. 907 908 If the JS8Call idle timeout is between 1 and 5 minutes, JS8Call will force the idle timeout to 5 minutes on the next application start or exit. 909 910 The maximum idle timeout is 1440 minutes (24 hours). 911 912 Disable the idle timeout by setting it to 0 (zero). 913 914 It is recommended that this function be called before calling *client.start()*. If this function is called after *client.start()* then the application will have to be restarted to utilize the new config file settings. See *client.restart()*. 915 916 Args: 917 timeout (int): Idle timeout in minutes 918 919 Returns: 920 int: Current idle timeout in minutes 921 922 Raises: 923 ValueError: Idle timeout must be between 0 and 1440 minutes 924 ''' 925 if timeout < 0 or timeout > 1440: 926 raise ValueError('Idle timeout must be between 0 and 1440 minutes') 927 928 self._client.config.set('Configuration', 'TxIdleWatchdog', timeout) 929 return self.get_idle_timeout()
Set JS8Call idle timeout.
If the JS8Call idle timeout is between 1 and 5 minutes, JS8Call will force the idle timeout to 5 minutes on the next application start or exit.
The maximum idle timeout is 1440 minutes (24 hours).
Disable the idle timeout by setting it to 0 (zero).
It is recommended that this function be called before calling client.start(). If this function is called after client.start() then the application will have to be restarted to utilize the new config file settings. See client.restart().
Arguments:
- timeout (int): Idle timeout in minutes
Returns:
int: Current idle timeout in minutes
Raises:
- ValueError: Idle timeout must be between 0 and 1440 minutes
931 def get_distance_units_miles(self): 932 '''Get JS8Call distance unit setting. 933 934 Returns: 935 bool: True if distance units are set to miles, False if km 936 ''' 937 return self._client.config.get('Configuration', 'Miles', bool)
Get JS8Call distance unit setting.
Returns:
bool: True if distance units are set to miles, False if km
939 def set_distance_units_miles(self, units_miles): 940 '''Set JS8Call distance unit setting. 941 942 Args: 943 units_miles (bool): Set units to miles if True, set to km if False 944 945 Returns: 946 bool: True if distance units are set to miles, False if km 947 ''' 948 self._client.config.set('Configuration', 'Miles', str(units_miles).lower()) 949 return self.get_distance_units_miles()
Set JS8Call distance unit setting.
Arguments:
- units_miles (bool): Set units to miles if True, set to km if False
Returns:
bool: True if distance units are set to miles, False if km
951 def get_distance_units(self): 952 '''Get JS8Call distance units. 953 954 Returns: 955 str: Configured distance units: 'mi' or 'km' 956 ''' 957 if self.get_distance_units_miles(): 958 return 'mi' 959 else: 960 return 'km'
Get JS8Call distance units.
Returns:
str: Configured distance units: 'mi' or 'km'
962 def set_distance_units(self, units): 963 ''' Set JS8Call distance units. 964 965 Args: 966 units (str): Distance units: 'mi', 'miles', 'km', or 'kilometers' 967 968 Returns: 969 str: Configured distance units: 'miles' or 'km' 970 ''' 971 if units.lower() in ['mi', 'miles']: 972 self.set_distance_units_miles(True) 973 return self.get_distance_units() 974 elif units.lower() in ['km', 'kilometers']: 975 self.set_distance_units_miles(False) 976 return self.get_distance_units() 977 else: 978 raise ValueError('Distance units must be: mi, miles, km, or kilometers')
Set JS8Call distance units.
Arguments:
- units (str): Distance units: 'mi', 'miles', 'km', or 'kilometers'
Returns:
str: Configured distance units: 'miles' or 'km'
980 def get_station_grid(self, update=False): 981 '''Get JS8Call grid square. 982 983 Args: 984 update (bool): Update if True or use local state if False, defaults to False 985 986 Returns: 987 str: JS8Call configured grid square 988 ''' 989 grid = self._client.js8call.get_state('grid') 990 991 if update or grid is None: 992 msg = Message() 993 msg.type = Message.STATION_GET_GRID 994 self._client.js8call.send(msg) 995 grid = self._client.js8call.watch('grid') 996 997 return grid
Get JS8Call grid square.
Arguments:
- update (bool): Update if True or use local state if False, defaults to False
Returns:
str: JS8Call configured grid square
999 def set_station_grid(self, grid): 1000 '''Set JS8Call grid square. 1001 1002 Args: 1003 grid (str): Grid square 1004 1005 Returns: 1006 str: JS8Call configured grid square 1007 ''' 1008 grid = grid.upper() 1009 msg = Message() 1010 msg.type = Message.STATION_SET_GRID 1011 msg.value = grid 1012 self._client.js8call.send(msg) 1013 time.sleep(self._client._set_get_delay) 1014 return self.get_station_grid(update = True)
Set JS8Call grid square.
Arguments:
- grid (str): Grid square
Returns:
str: JS8Call configured grid square
1016 def get_station_info(self, update=False): 1017 '''Get JS8Call station information. 1018 1019 Args: 1020 update (bool): Update if True or use local state if False, defaults to False 1021 1022 Returns: 1023 str: JS8Call configured station information 1024 ''' 1025 info = self._client.js8call.get_state('info') 1026 1027 if update or info is None: 1028 msg = Message() 1029 msg.type = Message.STATION_GET_INFO 1030 self._client.js8call.send(msg) 1031 info = self._client.js8call.watch('info') 1032 1033 return info
Get JS8Call station information.
Arguments:
- update (bool): Update if True or use local state if False, defaults to False
Returns:
str: JS8Call configured station information
1035 def set_station_info(self, info): 1036 '''Set JS8Call station information. 1037 1038 *info* updated via API (if connected) and set in JS8Call configuration file. 1039 1040 Args: 1041 info (str): Station information 1042 1043 Returns: 1044 str: JS8Call configured station information 1045 ''' 1046 if self._client.online: 1047 msg = Message() 1048 msg.type = Message.STATION_SET_INFO 1049 msg.value = info 1050 self._client.js8call.send(msg) 1051 time.sleep(self._client._set_get_delay) 1052 info = self.get_station_info(update = True) 1053 1054 # save to config file to preserve over restart 1055 self._client.config.set('Configuration', 'MyInfo', info) 1056 return info
Set JS8Call station information.
info updated via API (if connected) and set in JS8Call configuration file.
Arguments:
- info (str): Station information
Returns:
str: JS8Call configured station information
1058 def append_pyjs8call_to_station_info(self): 1059 '''Append pyjs8call info to station info 1060 1061 A string like ', PYJS8CALL V0.0.0' is appended to the current station info. 1062 Example: 'QRPLABS QDX, 40M DIPOLE 33FT, PYJS8CALL V0.2.2' 1063 1064 If a string like ', PYJS8CALL' or ',PYJS8CALL' is found in the current station info, that substring (and everything after it) is dropped before appending the new pyjs8call info. 1065 1066 Returns: 1067 str: JS8Call configured station information 1068 ''' 1069 info = self.get_station_info().upper() 1070 1071 if ', PYJS8CALL' in info: 1072 info = info.split(', PYJS8CALL')[0] 1073 elif ',PYJS8CALL' in info: 1074 info = info.split(',PYJS8CALL')[0] 1075 1076 info = '{}, PYJS8CALL {}'.format(info, pyjs8call.__version__) 1077 return self.set_station_info(info)
Append pyjs8call info to station info
A string like ', PYJS8CALL V0.0.0' is appended to the current station info. Example: 'QRPLABS QDX, 40M DIPOLE 33FT, PYJS8CALL V0.2.2'
If a string like ', PYJS8CALL' or ',PYJS8CALL' is found in the current station info, that substring (and everything after it) is dropped before appending the new pyjs8call info.
Returns:
str: JS8Call configured station information
1079 def get_bandwidth(self, speed=None): 1080 '''Get JS8Call signal bandwidth based on modem speed. 1081 1082 Uses JS8Call configured speed if no speed is given. 1083 1084 | Speed | Bandwidth | 1085 | -------- | -------- | 1086 | slow | 25 Hz | 1087 | normal | 50 Hz | 1088 | fast | 80 Hz | 1089 | turbo | 160 Hz | 1090 | ultra | 250 Hz | 1091 1092 Args: 1093 speed (str): Speed setting, defaults to None 1094 1095 Returns: 1096 int: Bandwidth of JS8Call signal 1097 ''' 1098 if speed is None: 1099 speed = self.get_speed() 1100 elif isinstance(speed, int): 1101 speed = self.submode_to_speed(speed) 1102 1103 bandwidths = {'slow':25, 'normal':50, 'fast':80, 'turbo':160, 'ultra':250} 1104 1105 if speed in bandwidths: 1106 return bandwidths[speed] 1107 else: 1108 raise ValueError('Invalid speed \'' + speed + '\'')
Get JS8Call signal bandwidth based on modem speed.
Uses JS8Call configured speed if no speed is given.
Speed | Bandwidth |
---|---|
slow | 25 Hz |
normal | 50 Hz |
fast | 80 Hz |
turbo | 160 Hz |
ultra | 250 Hz |
Arguments:
- speed (str): Speed setting, defaults to None
Returns:
int: Bandwidth of JS8Call signal
1110 def get_window_duration(self, speed=None): 1111 '''Get JS8Call rx/tx window duration based on modem speed. 1112 1113 Uses JS8Call configured speed if no speed is given. 1114 1115 | Speed | Duration | 1116 | -------- | -------- | 1117 | slow | 30 seconds | 1118 | normal | 15 seconds | 1119 | fast | 10 seconds | 1120 | turbo | 6 seconds | 1121 | ultra | 4 seconds | 1122 1123 Args: 1124 speed (str): Speed setting, defaults to None 1125 1126 Returns: 1127 int: Duration of JS8Call rx/tx window in seconds 1128 ''' 1129 if speed is None: 1130 speed = self.get_speed() 1131 elif isinstance(speed, int): 1132 speed = self.submode_to_speed(speed) 1133 1134 duration = {'slow': 30, 'normal': 15, 'fast': 10, 'turbo': 6, 'ultra':4} 1135 return duration[speed]
Get JS8Call rx/tx window duration based on modem speed.
Uses JS8Call configured speed if no speed is given.
Speed | Duration |
---|---|
slow | 30 seconds |
normal | 15 seconds |
fast | 10 seconds |
turbo | 6 seconds |
ultra | 4 seconds |
Arguments:
- speed (str): Speed setting, defaults to None
Returns:
int: Duration of JS8Call rx/tx window in seconds
1137 def enable_daily_restart(self, restart_time='02:00'): 1138 '''Enable daily JS8Call restart at specified time. 1139 1140 The intended use of this function is to allow the removal of the *timer.out* file, which grows in size until it consumes all available disk space. This file cannot be removed while the application is running, but is automatically removed during the pyjs8call restart process. 1141 1142 This function adds a schedule entry. See *pyjs8call.schedulemonitor* for more information. 1143 1144 Args: 1145 restart_time (str): Local restart time in 24-hour format (ex. '23:30'), defaults to '02:00' 1146 ''' 1147 # add schedule entry to restart application daily with no settings changes 1148 self._daily_restart_schedule = self._client.schedule.add(restart_time, restart=True)
Enable daily JS8Call restart at specified time.
The intended use of this function is to allow the removal of the timer.out file, which grows in size until it consumes all available disk space. This file cannot be removed while the application is running, but is automatically removed during the pyjs8call restart process.
This function adds a schedule entry. See pyjs8call.schedulemonitor for more information.
Arguments:
- restart_time (str): Local restart time in 24-hour format (ex. '23:30'), defaults to '02:00'
1150 def disable_daily_restart(self): 1151 '''Disable daily JS8Call restart. 1152 1153 This function removes the schedule entry created by *enable_daily_restart*. See *pyjs8call.schedulemonitor* for more information. 1154 ''' 1155 if self._daily_restart_schedule is None: 1156 return 1157 1158 self._client.schedule.remove(self._daily_restart_schedule.dict()['time'], schedule=self._daily_restart_schedule) 1159 self._daily_restart_schedule = None
Disable daily JS8Call restart.
This function removes the schedule entry created by enable_daily_restart. See pyjs8call.schedulemonitor for more information.
1161 def daily_restart_enabled(self): 1162 '''Whether daily JS8Call restart is enabled. 1163 1164 Returns: 1165 bool: True if associated schedule entry is set, False otherwise 1166 ''' 1167 if self._daily_restart_schedule is not None: 1168 return True 1169 else: 1170 return False
Whether daily JS8Call restart is enabled.
Returns:
bool: True if associated schedule entry is set, False otherwise
1172 def get_daily_restart_time(self): 1173 '''Get daily JS8Call restart time. 1174 1175 Returns: 1176 str or None: Local restart time in 24-hour format (ex. '23:30'), or None if not enabled 1177 ''' 1178 if self._daily_restart_schedule is None: 1179 return 1180 1181 return self._daily_restart_schedule.start.strftime('%H:%M')
Get daily JS8Call restart time.
Returns:
str or None: Local restart time in 24-hour format (ex. '23:30'), or None if not enabled