mirror of
https://github.com/smittix/intercept.git
synced 2026-07-06 00:28:12 -07:00
style: apply ruff-format to entire codebase
First-time run of ruff-format via pre-commit hook normalises quote style, trailing commas, and whitespace across 188 Python files. No logic changes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -21,13 +21,13 @@ from .parser import (
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
'FORMAT_CODES',
|
||||
'DISTRESS_NATURE_CODES',
|
||||
'TELECOMMAND_CODES',
|
||||
'CATEGORY_PRIORITY',
|
||||
'MID_COUNTRY_MAP',
|
||||
'parse_dsc_message',
|
||||
'get_country_from_mmsi',
|
||||
'get_distress_nature_text',
|
||||
'get_format_text',
|
||||
"FORMAT_CODES",
|
||||
"DISTRESS_NATURE_CODES",
|
||||
"TELECOMMAND_CODES",
|
||||
"CATEGORY_PRIORITY",
|
||||
"MID_COUNTRY_MAP",
|
||||
"parse_dsc_message",
|
||||
"get_country_from_mmsi",
|
||||
"get_distress_nature_text",
|
||||
"get_format_text",
|
||||
]
|
||||
|
||||
+347
-353
@@ -14,12 +14,12 @@ from __future__ import annotations
|
||||
# =============================================================================
|
||||
|
||||
FORMAT_CODES = {
|
||||
102: 'ALL_SHIPS', # All ships call
|
||||
112: 'INDIVIDUAL', # Individual call
|
||||
114: 'INDIVIDUAL_ACK', # Individual acknowledgement
|
||||
116: 'GROUP', # Group call (including geographic area)
|
||||
120: 'DISTRESS', # Distress alert
|
||||
123: 'ALL_SHIPS_URGENCY_SAFETY', # All ships urgency/safety
|
||||
102: "ALL_SHIPS", # All ships call
|
||||
112: "INDIVIDUAL", # Individual call
|
||||
114: "INDIVIDUAL_ACK", # Individual acknowledgement
|
||||
116: "GROUP", # Group call (including geographic area)
|
||||
120: "DISTRESS", # Distress alert
|
||||
123: "ALL_SHIPS_URGENCY_SAFETY", # All ships urgency/safety
|
||||
}
|
||||
|
||||
# Valid ITU-R M.493 format specifiers
|
||||
@@ -30,12 +30,12 @@ VALID_EOS = {117, 122, 127}
|
||||
|
||||
# Category priority (lower = higher priority)
|
||||
CATEGORY_PRIORITY = {
|
||||
'DISTRESS': 0,
|
||||
'ALL_SHIPS_URGENCY_SAFETY': 2,
|
||||
'ALL_SHIPS': 5,
|
||||
'GROUP': 5,
|
||||
'INDIVIDUAL': 5,
|
||||
'INDIVIDUAL_ACK': 5,
|
||||
"DISTRESS": 0,
|
||||
"ALL_SHIPS_URGENCY_SAFETY": 2,
|
||||
"ALL_SHIPS": 5,
|
||||
"GROUP": 5,
|
||||
"INDIVIDUAL": 5,
|
||||
"INDIVIDUAL_ACK": 5,
|
||||
}
|
||||
|
||||
|
||||
@@ -45,18 +45,18 @@ CATEGORY_PRIORITY = {
|
||||
# =============================================================================
|
||||
|
||||
DISTRESS_NATURE_CODES = {
|
||||
100: 'UNDESIGNATED', # Undesignated distress
|
||||
101: 'FIRE', # Fire, explosion
|
||||
102: 'FLOODING', # Flooding
|
||||
103: 'COLLISION', # Collision
|
||||
104: 'GROUNDING', # Grounding
|
||||
105: 'LISTING', # Listing, in danger of capsizing
|
||||
106: 'SINKING', # Sinking
|
||||
107: 'DISABLED', # Disabled and adrift
|
||||
108: 'ABANDONING', # Abandoning ship
|
||||
109: 'PIRACY', # Piracy/armed robbery attack
|
||||
110: 'MOB', # Man overboard
|
||||
112: 'EPIRB', # EPIRB emission
|
||||
100: "UNDESIGNATED", # Undesignated distress
|
||||
101: "FIRE", # Fire, explosion
|
||||
102: "FLOODING", # Flooding
|
||||
103: "COLLISION", # Collision
|
||||
104: "GROUNDING", # Grounding
|
||||
105: "LISTING", # Listing, in danger of capsizing
|
||||
106: "SINKING", # Sinking
|
||||
107: "DISABLED", # Disabled and adrift
|
||||
108: "ABANDONING", # Abandoning ship
|
||||
109: "PIRACY", # Piracy/armed robbery attack
|
||||
110: "MOB", # Man overboard
|
||||
112: "EPIRB", # EPIRB emission
|
||||
}
|
||||
|
||||
|
||||
@@ -67,26 +67,25 @@ DISTRESS_NATURE_CODES = {
|
||||
|
||||
TELECOMMAND_CODES = {
|
||||
# First telecommand (type of subsequent communication)
|
||||
100: 'F3E_G3E_ALL', # F3E/G3E all modes (VHF telephony)
|
||||
101: 'F3E_G3E_DUPLEX', # F3E/G3E duplex
|
||||
102: 'POLLING', # Polling
|
||||
103: 'UNABLE_TO_COMPLY', # Unable to comply
|
||||
104: 'END_OF_CALL', # End of call
|
||||
105: 'DATA', # Data
|
||||
106: 'J3E_TELEPHONY', # J3E telephony (SSB)
|
||||
107: 'DISTRESS_ACK', # Distress acknowledgement
|
||||
108: 'DISTRESS_RELAY', # Distress relay
|
||||
109: 'F1B_J2B_FEC', # F1B/J2B FEC NBDP telegraphy
|
||||
110: 'F1B_J2B_ARQ', # F1B/J2B ARQ NBDP telegraphy
|
||||
111: 'TEST', # Test
|
||||
112: 'SHIP_POSITION', # Ship position request
|
||||
113: 'NO_INFO', # No information
|
||||
118: 'FREQ_ANNOUNCEMENT', # Frequency announcement
|
||||
126: 'NO_REASON', # No reason given
|
||||
|
||||
100: "F3E_G3E_ALL", # F3E/G3E all modes (VHF telephony)
|
||||
101: "F3E_G3E_DUPLEX", # F3E/G3E duplex
|
||||
102: "POLLING", # Polling
|
||||
103: "UNABLE_TO_COMPLY", # Unable to comply
|
||||
104: "END_OF_CALL", # End of call
|
||||
105: "DATA", # Data
|
||||
106: "J3E_TELEPHONY", # J3E telephony (SSB)
|
||||
107: "DISTRESS_ACK", # Distress acknowledgement
|
||||
108: "DISTRESS_RELAY", # Distress relay
|
||||
109: "F1B_J2B_FEC", # F1B/J2B FEC NBDP telegraphy
|
||||
110: "F1B_J2B_ARQ", # F1B/J2B ARQ NBDP telegraphy
|
||||
111: "TEST", # Test
|
||||
112: "SHIP_POSITION", # Ship position request
|
||||
113: "NO_INFO", # No information
|
||||
118: "FREQ_ANNOUNCEMENT", # Frequency announcement
|
||||
126: "NO_REASON", # No reason given
|
||||
# Second telecommand (additional info)
|
||||
200: 'F3E_G3E_SIMPLEX', # Simplex VHF telephony requested
|
||||
201: 'POLL_RESPONSE', # Poll response
|
||||
200: "F3E_G3E_SIMPLEX", # Simplex VHF telephony requested
|
||||
201: "POLL_RESPONSE", # Poll response
|
||||
}
|
||||
|
||||
# Full 0-127 telecommand lookup (maps unknown codes to "UNKNOWN")
|
||||
@@ -106,14 +105,14 @@ MIN_SYMBOLS_FOR_FORMAT = 12
|
||||
|
||||
# Special symbols
|
||||
DSC_SYMBOLS = {
|
||||
120: 'DX', # Dot pattern (synchronization)
|
||||
121: 'RX', # Phasing sequence RX
|
||||
122: 'SX', # Phasing sequence SX
|
||||
123: 'S0', # Phasing sequence S0
|
||||
124: 'S1', # Phasing sequence S1
|
||||
125: 'S2', # Phasing sequence S2
|
||||
126: 'S3', # Phasing sequence S3
|
||||
127: 'EOS', # End of sequence
|
||||
120: "DX", # Dot pattern (synchronization)
|
||||
121: "RX", # Phasing sequence RX
|
||||
122: "SX", # Phasing sequence SX
|
||||
123: "S0", # Phasing sequence S0
|
||||
124: "S1", # Phasing sequence S1
|
||||
125: "S2", # Phasing sequence S2
|
||||
126: "S3", # Phasing sequence S3
|
||||
127: "EOS", # End of sequence
|
||||
}
|
||||
|
||||
|
||||
@@ -125,308 +124,303 @@ DSC_SYMBOLS = {
|
||||
|
||||
MID_COUNTRY_MAP = {
|
||||
# Americas
|
||||
'201': 'Albania',
|
||||
'202': 'Andorra',
|
||||
'203': 'Austria',
|
||||
'204': 'Azores',
|
||||
'205': 'Belgium',
|
||||
'206': 'Belarus',
|
||||
'207': 'Bulgaria',
|
||||
'208': 'Vatican City',
|
||||
'209': 'Cyprus',
|
||||
'210': 'Cyprus',
|
||||
'211': 'Germany',
|
||||
'212': 'Cyprus',
|
||||
'213': 'Georgia',
|
||||
'214': 'Moldova',
|
||||
'215': 'Malta',
|
||||
'216': 'Armenia',
|
||||
'218': 'Germany',
|
||||
'219': 'Denmark',
|
||||
'220': 'Denmark',
|
||||
'224': 'Spain',
|
||||
'225': 'Spain',
|
||||
'226': 'France',
|
||||
'227': 'France',
|
||||
'228': 'France',
|
||||
'229': 'Malta',
|
||||
'230': 'Finland',
|
||||
'231': 'Faroe Islands',
|
||||
'232': 'United Kingdom',
|
||||
'233': 'United Kingdom',
|
||||
'234': 'United Kingdom',
|
||||
'235': 'United Kingdom',
|
||||
'236': 'Gibraltar',
|
||||
'237': 'Greece',
|
||||
'238': 'Croatia',
|
||||
'239': 'Greece',
|
||||
'240': 'Greece',
|
||||
'241': 'Greece',
|
||||
'242': 'Morocco',
|
||||
'243': 'Hungary',
|
||||
'244': 'Netherlands',
|
||||
'245': 'Netherlands',
|
||||
'246': 'Netherlands',
|
||||
'247': 'Italy',
|
||||
'248': 'Malta',
|
||||
'249': 'Malta',
|
||||
'250': 'Ireland',
|
||||
'251': 'Iceland',
|
||||
'252': 'Liechtenstein',
|
||||
'253': 'Luxembourg',
|
||||
'254': 'Monaco',
|
||||
'255': 'Madeira',
|
||||
'256': 'Malta',
|
||||
'257': 'Norway',
|
||||
'258': 'Norway',
|
||||
'259': 'Norway',
|
||||
'261': 'Poland',
|
||||
'262': 'Montenegro',
|
||||
'263': 'Portugal',
|
||||
'264': 'Romania',
|
||||
'265': 'Sweden',
|
||||
'266': 'Sweden',
|
||||
'267': 'Slovakia',
|
||||
'268': 'San Marino',
|
||||
'269': 'Switzerland',
|
||||
'270': 'Czech Republic',
|
||||
'271': 'Turkey',
|
||||
'272': 'Ukraine',
|
||||
'273': 'Russia',
|
||||
'274': 'North Macedonia',
|
||||
'275': 'Latvia',
|
||||
'276': 'Estonia',
|
||||
'277': 'Lithuania',
|
||||
'278': 'Slovenia',
|
||||
'279': 'Serbia',
|
||||
|
||||
"201": "Albania",
|
||||
"202": "Andorra",
|
||||
"203": "Austria",
|
||||
"204": "Azores",
|
||||
"205": "Belgium",
|
||||
"206": "Belarus",
|
||||
"207": "Bulgaria",
|
||||
"208": "Vatican City",
|
||||
"209": "Cyprus",
|
||||
"210": "Cyprus",
|
||||
"211": "Germany",
|
||||
"212": "Cyprus",
|
||||
"213": "Georgia",
|
||||
"214": "Moldova",
|
||||
"215": "Malta",
|
||||
"216": "Armenia",
|
||||
"218": "Germany",
|
||||
"219": "Denmark",
|
||||
"220": "Denmark",
|
||||
"224": "Spain",
|
||||
"225": "Spain",
|
||||
"226": "France",
|
||||
"227": "France",
|
||||
"228": "France",
|
||||
"229": "Malta",
|
||||
"230": "Finland",
|
||||
"231": "Faroe Islands",
|
||||
"232": "United Kingdom",
|
||||
"233": "United Kingdom",
|
||||
"234": "United Kingdom",
|
||||
"235": "United Kingdom",
|
||||
"236": "Gibraltar",
|
||||
"237": "Greece",
|
||||
"238": "Croatia",
|
||||
"239": "Greece",
|
||||
"240": "Greece",
|
||||
"241": "Greece",
|
||||
"242": "Morocco",
|
||||
"243": "Hungary",
|
||||
"244": "Netherlands",
|
||||
"245": "Netherlands",
|
||||
"246": "Netherlands",
|
||||
"247": "Italy",
|
||||
"248": "Malta",
|
||||
"249": "Malta",
|
||||
"250": "Ireland",
|
||||
"251": "Iceland",
|
||||
"252": "Liechtenstein",
|
||||
"253": "Luxembourg",
|
||||
"254": "Monaco",
|
||||
"255": "Madeira",
|
||||
"256": "Malta",
|
||||
"257": "Norway",
|
||||
"258": "Norway",
|
||||
"259": "Norway",
|
||||
"261": "Poland",
|
||||
"262": "Montenegro",
|
||||
"263": "Portugal",
|
||||
"264": "Romania",
|
||||
"265": "Sweden",
|
||||
"266": "Sweden",
|
||||
"267": "Slovakia",
|
||||
"268": "San Marino",
|
||||
"269": "Switzerland",
|
||||
"270": "Czech Republic",
|
||||
"271": "Turkey",
|
||||
"272": "Ukraine",
|
||||
"273": "Russia",
|
||||
"274": "North Macedonia",
|
||||
"275": "Latvia",
|
||||
"276": "Estonia",
|
||||
"277": "Lithuania",
|
||||
"278": "Slovenia",
|
||||
"279": "Serbia",
|
||||
# North America
|
||||
'301': 'Anguilla',
|
||||
'303': 'USA',
|
||||
'304': 'Antigua and Barbuda',
|
||||
'305': 'Antigua and Barbuda',
|
||||
'306': 'Curacao',
|
||||
'307': 'Aruba',
|
||||
'308': 'Bahamas',
|
||||
'309': 'Bahamas',
|
||||
'310': 'Bermuda',
|
||||
'311': 'Bahamas',
|
||||
'312': 'Belize',
|
||||
'314': 'Barbados',
|
||||
'316': 'Canada',
|
||||
'319': 'Cayman Islands',
|
||||
'321': 'Costa Rica',
|
||||
'323': 'Cuba',
|
||||
'325': 'Dominica',
|
||||
'327': 'Dominican Republic',
|
||||
'329': 'Guadeloupe',
|
||||
'330': 'Grenada',
|
||||
'331': 'Greenland',
|
||||
'332': 'Guatemala',
|
||||
'334': 'Honduras',
|
||||
'336': 'Haiti',
|
||||
'338': 'USA',
|
||||
'339': 'Jamaica',
|
||||
'341': 'Saint Kitts and Nevis',
|
||||
'343': 'Saint Lucia',
|
||||
'345': 'Mexico',
|
||||
'347': 'Martinique',
|
||||
'348': 'Montserrat',
|
||||
'350': 'Nicaragua',
|
||||
'351': 'Panama',
|
||||
'352': 'Panama',
|
||||
'353': 'Panama',
|
||||
'354': 'Panama',
|
||||
'355': 'Panama',
|
||||
'356': 'Panama',
|
||||
'357': 'Panama',
|
||||
'358': 'Puerto Rico',
|
||||
'359': 'El Salvador',
|
||||
'361': 'Saint Pierre and Miquelon',
|
||||
'362': 'Trinidad and Tobago',
|
||||
'364': 'Turks and Caicos',
|
||||
'366': 'USA',
|
||||
'367': 'USA',
|
||||
'368': 'USA',
|
||||
'369': 'USA',
|
||||
'370': 'Panama',
|
||||
'371': 'Panama',
|
||||
'372': 'Panama',
|
||||
'373': 'Panama',
|
||||
'374': 'Panama',
|
||||
'375': 'Saint Vincent and the Grenadines',
|
||||
'376': 'Saint Vincent and the Grenadines',
|
||||
'377': 'Saint Vincent and the Grenadines',
|
||||
'378': 'British Virgin Islands',
|
||||
'379': 'US Virgin Islands',
|
||||
|
||||
"301": "Anguilla",
|
||||
"303": "USA",
|
||||
"304": "Antigua and Barbuda",
|
||||
"305": "Antigua and Barbuda",
|
||||
"306": "Curacao",
|
||||
"307": "Aruba",
|
||||
"308": "Bahamas",
|
||||
"309": "Bahamas",
|
||||
"310": "Bermuda",
|
||||
"311": "Bahamas",
|
||||
"312": "Belize",
|
||||
"314": "Barbados",
|
||||
"316": "Canada",
|
||||
"319": "Cayman Islands",
|
||||
"321": "Costa Rica",
|
||||
"323": "Cuba",
|
||||
"325": "Dominica",
|
||||
"327": "Dominican Republic",
|
||||
"329": "Guadeloupe",
|
||||
"330": "Grenada",
|
||||
"331": "Greenland",
|
||||
"332": "Guatemala",
|
||||
"334": "Honduras",
|
||||
"336": "Haiti",
|
||||
"338": "USA",
|
||||
"339": "Jamaica",
|
||||
"341": "Saint Kitts and Nevis",
|
||||
"343": "Saint Lucia",
|
||||
"345": "Mexico",
|
||||
"347": "Martinique",
|
||||
"348": "Montserrat",
|
||||
"350": "Nicaragua",
|
||||
"351": "Panama",
|
||||
"352": "Panama",
|
||||
"353": "Panama",
|
||||
"354": "Panama",
|
||||
"355": "Panama",
|
||||
"356": "Panama",
|
||||
"357": "Panama",
|
||||
"358": "Puerto Rico",
|
||||
"359": "El Salvador",
|
||||
"361": "Saint Pierre and Miquelon",
|
||||
"362": "Trinidad and Tobago",
|
||||
"364": "Turks and Caicos",
|
||||
"366": "USA",
|
||||
"367": "USA",
|
||||
"368": "USA",
|
||||
"369": "USA",
|
||||
"370": "Panama",
|
||||
"371": "Panama",
|
||||
"372": "Panama",
|
||||
"373": "Panama",
|
||||
"374": "Panama",
|
||||
"375": "Saint Vincent and the Grenadines",
|
||||
"376": "Saint Vincent and the Grenadines",
|
||||
"377": "Saint Vincent and the Grenadines",
|
||||
"378": "British Virgin Islands",
|
||||
"379": "US Virgin Islands",
|
||||
# Asia
|
||||
'401': 'Afghanistan',
|
||||
'403': 'Saudi Arabia',
|
||||
'405': 'Bangladesh',
|
||||
'408': 'Bahrain',
|
||||
'410': 'Bhutan',
|
||||
'412': 'China',
|
||||
'413': 'China',
|
||||
'414': 'China',
|
||||
'416': 'Taiwan',
|
||||
'417': 'Sri Lanka',
|
||||
'419': 'India',
|
||||
'422': 'Iran',
|
||||
'423': 'Azerbaijan',
|
||||
'425': 'Iraq',
|
||||
'428': 'Israel',
|
||||
'431': 'Japan',
|
||||
'432': 'Japan',
|
||||
'434': 'Turkmenistan',
|
||||
'436': 'Kazakhstan',
|
||||
'437': 'Uzbekistan',
|
||||
'438': 'Jordan',
|
||||
'440': 'South Korea',
|
||||
'441': 'South Korea',
|
||||
'443': 'Palestine',
|
||||
'445': 'North Korea',
|
||||
'447': 'Kuwait',
|
||||
'450': 'Lebanon',
|
||||
'451': 'Kyrgyzstan',
|
||||
'453': 'Macao',
|
||||
'455': 'Maldives',
|
||||
'457': 'Mongolia',
|
||||
'459': 'Nepal',
|
||||
'461': 'Oman',
|
||||
'463': 'Pakistan',
|
||||
'466': 'Qatar',
|
||||
'468': 'Syria',
|
||||
'470': 'UAE',
|
||||
'471': 'UAE',
|
||||
'472': 'Tajikistan',
|
||||
'473': 'Yemen',
|
||||
'475': 'Yemen',
|
||||
'477': 'Hong Kong',
|
||||
'478': 'Bosnia and Herzegovina',
|
||||
|
||||
"401": "Afghanistan",
|
||||
"403": "Saudi Arabia",
|
||||
"405": "Bangladesh",
|
||||
"408": "Bahrain",
|
||||
"410": "Bhutan",
|
||||
"412": "China",
|
||||
"413": "China",
|
||||
"414": "China",
|
||||
"416": "Taiwan",
|
||||
"417": "Sri Lanka",
|
||||
"419": "India",
|
||||
"422": "Iran",
|
||||
"423": "Azerbaijan",
|
||||
"425": "Iraq",
|
||||
"428": "Israel",
|
||||
"431": "Japan",
|
||||
"432": "Japan",
|
||||
"434": "Turkmenistan",
|
||||
"436": "Kazakhstan",
|
||||
"437": "Uzbekistan",
|
||||
"438": "Jordan",
|
||||
"440": "South Korea",
|
||||
"441": "South Korea",
|
||||
"443": "Palestine",
|
||||
"445": "North Korea",
|
||||
"447": "Kuwait",
|
||||
"450": "Lebanon",
|
||||
"451": "Kyrgyzstan",
|
||||
"453": "Macao",
|
||||
"455": "Maldives",
|
||||
"457": "Mongolia",
|
||||
"459": "Nepal",
|
||||
"461": "Oman",
|
||||
"463": "Pakistan",
|
||||
"466": "Qatar",
|
||||
"468": "Syria",
|
||||
"470": "UAE",
|
||||
"471": "UAE",
|
||||
"472": "Tajikistan",
|
||||
"473": "Yemen",
|
||||
"475": "Yemen",
|
||||
"477": "Hong Kong",
|
||||
"478": "Bosnia and Herzegovina",
|
||||
# Oceania
|
||||
'501': 'Adelie Land',
|
||||
'503': 'Australia',
|
||||
'506': 'Myanmar',
|
||||
'508': 'Brunei',
|
||||
'510': 'Micronesia',
|
||||
'511': 'Palau',
|
||||
'512': 'New Zealand',
|
||||
'514': 'Cambodia',
|
||||
'515': 'Cambodia',
|
||||
'516': 'Christmas Island',
|
||||
'518': 'Cook Islands',
|
||||
'520': 'Fiji',
|
||||
'523': 'Cocos Islands',
|
||||
'525': 'Indonesia',
|
||||
'529': 'Kiribati',
|
||||
'531': 'Laos',
|
||||
'533': 'Malaysia',
|
||||
'536': 'Northern Mariana Islands',
|
||||
'538': 'Marshall Islands',
|
||||
'540': 'New Caledonia',
|
||||
'542': 'Niue',
|
||||
'544': 'Nauru',
|
||||
'546': 'French Polynesia',
|
||||
'548': 'Philippines',
|
||||
'550': 'Timor-Leste',
|
||||
'553': 'Papua New Guinea',
|
||||
'555': 'Pitcairn Island',
|
||||
'557': 'Solomon Islands',
|
||||
'559': 'American Samoa',
|
||||
'561': 'Samoa',
|
||||
'563': 'Singapore',
|
||||
'564': 'Singapore',
|
||||
'565': 'Singapore',
|
||||
'566': 'Singapore',
|
||||
'567': 'Thailand',
|
||||
'570': 'Tonga',
|
||||
'572': 'Tuvalu',
|
||||
'574': 'Vietnam',
|
||||
'576': 'Vanuatu',
|
||||
'577': 'Vanuatu',
|
||||
'578': 'Wallis and Futuna',
|
||||
|
||||
"501": "Adelie Land",
|
||||
"503": "Australia",
|
||||
"506": "Myanmar",
|
||||
"508": "Brunei",
|
||||
"510": "Micronesia",
|
||||
"511": "Palau",
|
||||
"512": "New Zealand",
|
||||
"514": "Cambodia",
|
||||
"515": "Cambodia",
|
||||
"516": "Christmas Island",
|
||||
"518": "Cook Islands",
|
||||
"520": "Fiji",
|
||||
"523": "Cocos Islands",
|
||||
"525": "Indonesia",
|
||||
"529": "Kiribati",
|
||||
"531": "Laos",
|
||||
"533": "Malaysia",
|
||||
"536": "Northern Mariana Islands",
|
||||
"538": "Marshall Islands",
|
||||
"540": "New Caledonia",
|
||||
"542": "Niue",
|
||||
"544": "Nauru",
|
||||
"546": "French Polynesia",
|
||||
"548": "Philippines",
|
||||
"550": "Timor-Leste",
|
||||
"553": "Papua New Guinea",
|
||||
"555": "Pitcairn Island",
|
||||
"557": "Solomon Islands",
|
||||
"559": "American Samoa",
|
||||
"561": "Samoa",
|
||||
"563": "Singapore",
|
||||
"564": "Singapore",
|
||||
"565": "Singapore",
|
||||
"566": "Singapore",
|
||||
"567": "Thailand",
|
||||
"570": "Tonga",
|
||||
"572": "Tuvalu",
|
||||
"574": "Vietnam",
|
||||
"576": "Vanuatu",
|
||||
"577": "Vanuatu",
|
||||
"578": "Wallis and Futuna",
|
||||
# Africa
|
||||
'601': 'South Africa',
|
||||
'603': 'Angola',
|
||||
'605': 'Algeria',
|
||||
'607': 'St. Paul and Amsterdam Islands',
|
||||
'608': 'Ascension Island',
|
||||
'609': 'Burundi',
|
||||
'610': 'Benin',
|
||||
'611': 'Botswana',
|
||||
'612': 'Central African Republic',
|
||||
'613': 'Cameroon',
|
||||
'615': 'Congo',
|
||||
'616': 'Comoros',
|
||||
'617': 'Cabo Verde',
|
||||
'618': 'Crozet Archipelago',
|
||||
'619': 'Ivory Coast',
|
||||
'620': 'Comoros',
|
||||
'621': 'Djibouti',
|
||||
'622': 'Egypt',
|
||||
'624': 'Ethiopia',
|
||||
'625': 'Eritrea',
|
||||
'626': 'Gabon',
|
||||
'627': 'Ghana',
|
||||
'629': 'Gambia',
|
||||
'630': 'Guinea-Bissau',
|
||||
'631': 'Equatorial Guinea',
|
||||
'632': 'Guinea',
|
||||
'633': 'Burkina Faso',
|
||||
'634': 'Kenya',
|
||||
'635': 'Kerguelen Islands',
|
||||
'636': 'Liberia',
|
||||
'637': 'Liberia',
|
||||
'638': 'South Sudan',
|
||||
'642': 'Libya',
|
||||
'644': 'Lesotho',
|
||||
'645': 'Mauritius',
|
||||
'647': 'Madagascar',
|
||||
'649': 'Mali',
|
||||
'650': 'Mozambique',
|
||||
'654': 'Mauritania',
|
||||
'655': 'Malawi',
|
||||
'656': 'Niger',
|
||||
'657': 'Nigeria',
|
||||
'659': 'Namibia',
|
||||
'660': 'Reunion',
|
||||
'661': 'Rwanda',
|
||||
'662': 'Sudan',
|
||||
'663': 'Senegal',
|
||||
'664': 'Seychelles',
|
||||
'665': 'Saint Helena',
|
||||
'666': 'Somalia',
|
||||
'667': 'Sierra Leone',
|
||||
'668': 'Sao Tome and Principe',
|
||||
'669': 'Swaziland',
|
||||
'670': 'Chad',
|
||||
'671': 'Togo',
|
||||
'672': 'Tunisia',
|
||||
'674': 'Tanzania',
|
||||
'675': 'Uganda',
|
||||
'676': 'Democratic Republic of Congo',
|
||||
'677': 'Tanzania',
|
||||
'678': 'Zambia',
|
||||
'679': 'Zimbabwe',
|
||||
|
||||
"601": "South Africa",
|
||||
"603": "Angola",
|
||||
"605": "Algeria",
|
||||
"607": "St. Paul and Amsterdam Islands",
|
||||
"608": "Ascension Island",
|
||||
"609": "Burundi",
|
||||
"610": "Benin",
|
||||
"611": "Botswana",
|
||||
"612": "Central African Republic",
|
||||
"613": "Cameroon",
|
||||
"615": "Congo",
|
||||
"616": "Comoros",
|
||||
"617": "Cabo Verde",
|
||||
"618": "Crozet Archipelago",
|
||||
"619": "Ivory Coast",
|
||||
"620": "Comoros",
|
||||
"621": "Djibouti",
|
||||
"622": "Egypt",
|
||||
"624": "Ethiopia",
|
||||
"625": "Eritrea",
|
||||
"626": "Gabon",
|
||||
"627": "Ghana",
|
||||
"629": "Gambia",
|
||||
"630": "Guinea-Bissau",
|
||||
"631": "Equatorial Guinea",
|
||||
"632": "Guinea",
|
||||
"633": "Burkina Faso",
|
||||
"634": "Kenya",
|
||||
"635": "Kerguelen Islands",
|
||||
"636": "Liberia",
|
||||
"637": "Liberia",
|
||||
"638": "South Sudan",
|
||||
"642": "Libya",
|
||||
"644": "Lesotho",
|
||||
"645": "Mauritius",
|
||||
"647": "Madagascar",
|
||||
"649": "Mali",
|
||||
"650": "Mozambique",
|
||||
"654": "Mauritania",
|
||||
"655": "Malawi",
|
||||
"656": "Niger",
|
||||
"657": "Nigeria",
|
||||
"659": "Namibia",
|
||||
"660": "Reunion",
|
||||
"661": "Rwanda",
|
||||
"662": "Sudan",
|
||||
"663": "Senegal",
|
||||
"664": "Seychelles",
|
||||
"665": "Saint Helena",
|
||||
"666": "Somalia",
|
||||
"667": "Sierra Leone",
|
||||
"668": "Sao Tome and Principe",
|
||||
"669": "Swaziland",
|
||||
"670": "Chad",
|
||||
"671": "Togo",
|
||||
"672": "Tunisia",
|
||||
"674": "Tanzania",
|
||||
"675": "Uganda",
|
||||
"676": "Democratic Republic of Congo",
|
||||
"677": "Tanzania",
|
||||
"678": "Zambia",
|
||||
"679": "Zimbabwe",
|
||||
# South America
|
||||
'701': 'Argentina',
|
||||
'710': 'Brazil',
|
||||
'720': 'Bolivia',
|
||||
'725': 'Chile',
|
||||
'730': 'Colombia',
|
||||
'735': 'Ecuador',
|
||||
'740': 'Falkland Islands',
|
||||
'745': 'Guiana',
|
||||
'750': 'Guyana',
|
||||
'755': 'Paraguay',
|
||||
'760': 'Peru',
|
||||
'765': 'Suriname',
|
||||
'770': 'Uruguay',
|
||||
'775': 'Venezuela',
|
||||
"701": "Argentina",
|
||||
"710": "Brazil",
|
||||
"720": "Bolivia",
|
||||
"725": "Chile",
|
||||
"730": "Colombia",
|
||||
"735": "Ecuador",
|
||||
"740": "Falkland Islands",
|
||||
"745": "Guiana",
|
||||
"750": "Guyana",
|
||||
"755": "Paraguay",
|
||||
"760": "Peru",
|
||||
"765": "Suriname",
|
||||
"770": "Uruguay",
|
||||
"775": "Venezuela",
|
||||
}
|
||||
|
||||
|
||||
@@ -435,9 +429,9 @@ MID_COUNTRY_MAP = {
|
||||
# =============================================================================
|
||||
|
||||
VHF_CHANNELS = {
|
||||
6: 156.300, # Intership safety
|
||||
8: 156.400, # Commercial working
|
||||
9: 156.450, # Calling
|
||||
6: 156.300, # Intership safety
|
||||
8: 156.400, # Commercial working
|
||||
9: 156.450, # Calling
|
||||
10: 156.500, # Commercial working
|
||||
12: 156.600, # Port operations
|
||||
13: 156.650, # Bridge-to-bridge navigation safety
|
||||
@@ -469,5 +463,5 @@ DSC_AUDIO_SAMPLE_RATE = 48000
|
||||
|
||||
# Frame structure
|
||||
DSC_DOT_PATTERN_LENGTH = 200 # 200 bits of alternating pattern
|
||||
DSC_PHASING_LENGTH = 7 # 7 symbols phasing sequence
|
||||
DSC_MESSAGE_MAX_SYMBOLS = 180 # Maximum message length in symbols
|
||||
DSC_PHASING_LENGTH = 7 # 7 symbols phasing sequence
|
||||
DSC_MESSAGE_MAX_SYMBOLS = 180 # Maximum message length in symbols
|
||||
|
||||
+38
-48
@@ -47,12 +47,8 @@ from .constants import (
|
||||
)
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.WARNING,
|
||||
format='%(asctime)s [%(levelname)s] %(message)s',
|
||||
stream=sys.stderr
|
||||
)
|
||||
logger = logging.getLogger('dsc.decoder')
|
||||
logging.basicConfig(level=logging.WARNING, format="%(asctime)s [%(levelname)s] %(message)s", stream=sys.stderr)
|
||||
logger = logging.getLogger("dsc.decoder")
|
||||
|
||||
|
||||
class DSCDecoder:
|
||||
@@ -75,7 +71,7 @@ class DSCDecoder:
|
||||
nyq = sample_rate / 2
|
||||
low = 1100 / nyq
|
||||
high = 2300 / nyq
|
||||
self.bp_b, self.bp_a = scipy_signal.butter(4, [low, high], btype='band')
|
||||
self.bp_b, self.bp_a = scipy_signal.butter(4, [low, high], btype="band")
|
||||
|
||||
# Build FSK correlators
|
||||
self._build_correlators()
|
||||
@@ -162,8 +158,8 @@ class DSCDecoder:
|
||||
break
|
||||
|
||||
# Correlate with mark and space references
|
||||
mark_corr = np.abs(np.correlate(segment, self.mark_ref, mode='valid'))
|
||||
space_corr = np.abs(np.correlate(segment, self.space_ref, mode='valid'))
|
||||
mark_corr = np.abs(np.correlate(segment, self.mark_ref, mode="valid"))
|
||||
space_corr = np.abs(np.correlate(segment, self.space_ref, mode="valid"))
|
||||
|
||||
# Decision: mark (1) if mark correlation > space correlation
|
||||
if np.max(mark_corr) > np.max(space_corr):
|
||||
@@ -304,7 +300,7 @@ class DSCDecoder:
|
||||
return None
|
||||
|
||||
# Decode the message from symbols
|
||||
return self._decode_symbols(symbols[:eos_index + 1])
|
||||
return self._decode_symbols(symbols[: eos_index + 1])
|
||||
|
||||
def _bits_to_symbol(self, bits: list[int]) -> int:
|
||||
"""
|
||||
@@ -322,7 +318,7 @@ class DSCDecoder:
|
||||
value = 0
|
||||
for i in range(7):
|
||||
if bits[i]:
|
||||
value |= (1 << i)
|
||||
value |= 1 << i
|
||||
|
||||
# Validate check bits: total number of 1s should be even
|
||||
ones = sum(bits)
|
||||
@@ -354,23 +350,23 @@ class DSCDecoder:
|
||||
try:
|
||||
# Format specifier (first non-phasing symbol)
|
||||
format_code = symbols[0]
|
||||
format_text = FORMAT_CODES.get(format_code, f'UNKNOWN-{format_code}')
|
||||
format_text = FORMAT_CODES.get(format_code, f"UNKNOWN-{format_code}")
|
||||
|
||||
# Derive category from format specifier per ITU-R M.493
|
||||
if format_code == 120:
|
||||
category = 'DISTRESS'
|
||||
category = "DISTRESS"
|
||||
elif format_code == 123:
|
||||
category = 'ALL_SHIPS_URGENCY_SAFETY'
|
||||
category = "ALL_SHIPS_URGENCY_SAFETY"
|
||||
elif format_code == 102:
|
||||
category = 'ALL_SHIPS'
|
||||
category = "ALL_SHIPS"
|
||||
elif format_code == 116:
|
||||
category = 'GROUP'
|
||||
category = "GROUP"
|
||||
elif format_code == 112:
|
||||
category = 'INDIVIDUAL'
|
||||
category = "INDIVIDUAL"
|
||||
elif format_code == 114:
|
||||
category = 'INDIVIDUAL_ACK'
|
||||
category = "INDIVIDUAL_ACK"
|
||||
else:
|
||||
category = FORMAT_CODES.get(format_code, 'UNKNOWN')
|
||||
category = FORMAT_CODES.get(format_code, "UNKNOWN")
|
||||
|
||||
# Decode MMSI from symbols 1-5 (destination/address)
|
||||
dest_mmsi = self._decode_mmsi(symbols[1:6])
|
||||
@@ -383,40 +379,38 @@ class DSCDecoder:
|
||||
return None
|
||||
|
||||
message = {
|
||||
'type': 'dsc',
|
||||
'format': format_code,
|
||||
'format_text': format_text,
|
||||
'category': category,
|
||||
'source_mmsi': source_mmsi,
|
||||
'dest_mmsi': dest_mmsi,
|
||||
'timestamp': datetime.utcnow().isoformat() + 'Z',
|
||||
"type": "dsc",
|
||||
"format": format_code,
|
||||
"format_text": format_text,
|
||||
"category": category,
|
||||
"source_mmsi": source_mmsi,
|
||||
"dest_mmsi": dest_mmsi,
|
||||
"timestamp": datetime.utcnow().isoformat() + "Z",
|
||||
}
|
||||
|
||||
# Parse additional fields based on format
|
||||
remaining = symbols[11:-1] # Exclude EOS
|
||||
|
||||
if category in ('DISTRESS', 'DISTRESS_RELAY'):
|
||||
if category in ("DISTRESS", "DISTRESS_RELAY"):
|
||||
# Distress messages have nature and position
|
||||
if len(remaining) >= 1:
|
||||
message['nature'] = remaining[0]
|
||||
message['nature_text'] = DISTRESS_NATURE_CODES.get(
|
||||
remaining[0], f'UNKNOWN-{remaining[0]}'
|
||||
)
|
||||
message["nature"] = remaining[0]
|
||||
message["nature_text"] = DISTRESS_NATURE_CODES.get(remaining[0], f"UNKNOWN-{remaining[0]}")
|
||||
|
||||
# Try to decode position
|
||||
if len(remaining) >= 11:
|
||||
position = self._decode_position(remaining[1:11])
|
||||
if position:
|
||||
message['position'] = position
|
||||
message["position"] = position
|
||||
|
||||
# Telecommand fields (last two before EOS) — only for formats
|
||||
# that carry telecommand fields per ITU-R M.493
|
||||
if format_code in TELECOMMAND_FORMATS and len(remaining) >= 2:
|
||||
message['telecommand1'] = remaining[-2]
|
||||
message['telecommand2'] = remaining[-1]
|
||||
message["telecommand1"] = remaining[-2]
|
||||
message["telecommand2"] = remaining[-1]
|
||||
|
||||
# Add raw data for debugging
|
||||
message['raw'] = ''.join(f'{s:03d}' for s in symbols)
|
||||
message["raw"] = "".join(f"{s:03d}" for s in symbols)
|
||||
|
||||
logger.info(f"Decoded DSC: {category} from {source_mmsi}")
|
||||
return message
|
||||
@@ -441,9 +435,9 @@ class DSCDecoder:
|
||||
if sym < 0 or sym > 99:
|
||||
return None
|
||||
# Each symbol is 2 BCD digits
|
||||
digits.append(f'{sym:02d}')
|
||||
digits.append(f"{sym:02d}")
|
||||
|
||||
mmsi = ''.join(digits)
|
||||
mmsi = "".join(digits)
|
||||
# MMSI is 9 digits - trim the leading digit from the 10-digit
|
||||
# BCD result since the first symbol's high digit is always 0
|
||||
if len(mmsi) > 9:
|
||||
@@ -487,7 +481,7 @@ class DSCDecoder:
|
||||
lat = lat_sign * (lat_deg + lat_min / 60.0)
|
||||
lon = lon_sign * (lon_deg + lon_min / 60.0)
|
||||
|
||||
return {'lat': round(lat, 6), 'lon': round(lon, 6)}
|
||||
return {"lat": round(lat, 6), "lon": round(lon, 6)}
|
||||
|
||||
except Exception:
|
||||
return None
|
||||
@@ -517,20 +511,16 @@ def read_audio_stdin() -> Generator[bytes, None, None]:
|
||||
def main():
|
||||
"""Main entry point for DSC decoder."""
|
||||
parser = argparse.ArgumentParser(
|
||||
description='DSC (Digital Selective Calling) decoder',
|
||||
epilog='Reads 48kHz 16-bit signed PCM audio from stdin'
|
||||
description="DSC (Digital Selective Calling) decoder", epilog="Reads 48kHz 16-bit signed PCM audio from stdin"
|
||||
)
|
||||
parser.add_argument(
|
||||
'-r', '--sample-rate',
|
||||
"-r",
|
||||
"--sample-rate",
|
||||
type=int,
|
||||
default=DSC_AUDIO_SAMPLE_RATE,
|
||||
help=f'Audio sample rate (default: {DSC_AUDIO_SAMPLE_RATE})'
|
||||
)
|
||||
parser.add_argument(
|
||||
'-v', '--verbose',
|
||||
action='store_true',
|
||||
help='Enable verbose logging'
|
||||
help=f"Audio sample rate (default: {DSC_AUDIO_SAMPLE_RATE})",
|
||||
)
|
||||
parser.add_argument("-v", "--verbose", action="store_true", help="Enable verbose logging")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.verbose:
|
||||
@@ -551,5 +541,5 @@ def main():
|
||||
logger.info("DSC decoder stopped")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
+79
-75
@@ -23,7 +23,7 @@ from .constants import (
|
||||
VALID_FORMAT_SPECIFIERS,
|
||||
)
|
||||
|
||||
logger = logging.getLogger('intercept.dsc.parser')
|
||||
logger = logging.getLogger("intercept.dsc.parser")
|
||||
|
||||
|
||||
def get_country_from_mmsi(mmsi: str) -> str | None:
|
||||
@@ -47,13 +47,13 @@ def get_country_from_mmsi(mmsi: str) -> str | None:
|
||||
return MID_COUNTRY_MAP[mid]
|
||||
|
||||
# Coast station MMSI: starts with 00 + MID
|
||||
if mmsi.startswith('00') and len(mmsi) >= 5:
|
||||
if mmsi.startswith("00") and len(mmsi) >= 5:
|
||||
mid = mmsi[2:5]
|
||||
if mid in MID_COUNTRY_MAP:
|
||||
return MID_COUNTRY_MAP[mid]
|
||||
|
||||
# Group ship station MMSI: starts with 0 + MID
|
||||
if mmsi.startswith('0') and len(mmsi) >= 4:
|
||||
if mmsi.startswith("0") and len(mmsi) >= 4:
|
||||
mid = mmsi[1:4]
|
||||
if mid in MID_COUNTRY_MAP:
|
||||
return MID_COUNTRY_MAP[mid]
|
||||
@@ -69,7 +69,7 @@ def get_distress_nature_text(code: int | str) -> str:
|
||||
except ValueError:
|
||||
return str(code)
|
||||
|
||||
return DISTRESS_NATURE_CODES.get(code, f'UNKNOWN ({code})')
|
||||
return DISTRESS_NATURE_CODES.get(code, f"UNKNOWN ({code})")
|
||||
|
||||
|
||||
def get_format_text(code: int | str) -> str:
|
||||
@@ -80,7 +80,7 @@ def get_format_text(code: int | str) -> str:
|
||||
except ValueError:
|
||||
return str(code)
|
||||
|
||||
return FORMAT_CODES.get(code, f'UNKNOWN ({code})')
|
||||
return FORMAT_CODES.get(code, f"UNKNOWN ({code})")
|
||||
|
||||
|
||||
def get_telecommand_text(code: int | str) -> str:
|
||||
@@ -91,7 +91,7 @@ def get_telecommand_text(code: int | str) -> str:
|
||||
except ValueError:
|
||||
return str(code)
|
||||
|
||||
return TELECOMMAND_CODES.get(code, f'UNKNOWN ({code})')
|
||||
return TELECOMMAND_CODES.get(code, f"UNKNOWN ({code})")
|
||||
|
||||
|
||||
def get_category_priority(category: str) -> int:
|
||||
@@ -135,25 +135,25 @@ def parse_dsc_message(raw_line: str) -> dict[str, Any] | None:
|
||||
return None
|
||||
|
||||
# Validate required fields
|
||||
if data.get('type') != 'dsc':
|
||||
if data.get("type") != "dsc":
|
||||
return None
|
||||
|
||||
if 'source_mmsi' not in data:
|
||||
if "source_mmsi" not in data:
|
||||
return None
|
||||
|
||||
# ITU-R M.493 validation: format specifier must be valid
|
||||
format_code = data.get('format')
|
||||
format_code = data.get("format")
|
||||
if format_code not in VALID_FORMAT_SPECIFIERS:
|
||||
logger.debug(f"Rejected DSC: invalid format specifier {format_code}")
|
||||
return None
|
||||
|
||||
# Validate MMSIs
|
||||
source_mmsi = str(data.get('source_mmsi', ''))
|
||||
source_mmsi = str(data.get("source_mmsi", ""))
|
||||
if not validate_mmsi(source_mmsi):
|
||||
logger.debug(f"Rejected DSC: invalid source MMSI {source_mmsi}")
|
||||
return None
|
||||
|
||||
dest_mmsi_val = data.get('dest_mmsi')
|
||||
dest_mmsi_val = data.get("dest_mmsi")
|
||||
if dest_mmsi_val is not None:
|
||||
dest_mmsi_str = str(dest_mmsi_val)
|
||||
if not validate_mmsi(dest_mmsi_str):
|
||||
@@ -161,10 +161,10 @@ def parse_dsc_message(raw_line: str) -> dict[str, Any] | None:
|
||||
return None
|
||||
|
||||
# Validate raw field structure if present
|
||||
raw = data.get('raw')
|
||||
raw = data.get("raw")
|
||||
if raw is not None:
|
||||
raw_str = str(raw)
|
||||
if not re.match(r'^\d+$', raw_str):
|
||||
if not re.match(r"^\d+$", raw_str):
|
||||
logger.debug("Rejected DSC: raw field contains non-digits")
|
||||
return None
|
||||
if len(raw_str) % 3 != 0:
|
||||
@@ -178,7 +178,7 @@ def parse_dsc_message(raw_line: str) -> dict[str, Any] | None:
|
||||
return None
|
||||
|
||||
# Validate telecommand values if present (must be 100-127)
|
||||
for tc_field in ('telecommand1', 'telecommand2'):
|
||||
for tc_field in ("telecommand1", "telecommand2"):
|
||||
tc_val = data.get(tc_field)
|
||||
if tc_val is not None:
|
||||
try:
|
||||
@@ -192,65 +192,69 @@ def parse_dsc_message(raw_line: str) -> dict[str, Any] | None:
|
||||
|
||||
# Build parsed message
|
||||
msg = {
|
||||
'type': 'dsc_message',
|
||||
'source_mmsi': source_mmsi,
|
||||
'dest_mmsi': str(data.get('dest_mmsi', '')) if data.get('dest_mmsi') is not None else None,
|
||||
'format_code': format_code,
|
||||
'format_text': get_format_text(format_code),
|
||||
'category': data.get('category', 'UNKNOWN').upper(),
|
||||
'timestamp': data.get('timestamp') or datetime.utcnow().isoformat(),
|
||||
"type": "dsc_message",
|
||||
"source_mmsi": source_mmsi,
|
||||
"dest_mmsi": str(data.get("dest_mmsi", "")) if data.get("dest_mmsi") is not None else None,
|
||||
"format_code": format_code,
|
||||
"format_text": get_format_text(format_code),
|
||||
"category": data.get("category", "UNKNOWN").upper(),
|
||||
"timestamp": data.get("timestamp") or datetime.utcnow().isoformat(),
|
||||
}
|
||||
|
||||
# Add country from MMSI
|
||||
country = get_country_from_mmsi(msg['source_mmsi'])
|
||||
country = get_country_from_mmsi(msg["source_mmsi"])
|
||||
if country:
|
||||
msg['source_country'] = country
|
||||
msg["source_country"] = country
|
||||
|
||||
# Add distress nature if present
|
||||
if data.get('nature') is not None:
|
||||
msg['nature_code'] = data['nature']
|
||||
msg['nature_of_distress'] = get_distress_nature_text(data['nature'])
|
||||
if data.get("nature") is not None:
|
||||
msg["nature_code"] = data["nature"]
|
||||
msg["nature_of_distress"] = get_distress_nature_text(data["nature"])
|
||||
|
||||
# Add position if present
|
||||
position = data.get('position')
|
||||
position = data.get("position")
|
||||
if position and isinstance(position, dict):
|
||||
lat = position.get('lat')
|
||||
lon = position.get('lon')
|
||||
lat = position.get("lat")
|
||||
lon = position.get("lon")
|
||||
if lat is not None and lon is not None:
|
||||
try:
|
||||
msg['latitude'] = float(lat)
|
||||
msg['longitude'] = float(lon)
|
||||
msg["latitude"] = float(lat)
|
||||
msg["longitude"] = float(lon)
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
# Add telecommand info
|
||||
if data.get('telecommand1') is not None:
|
||||
msg['telecommand1'] = data['telecommand1']
|
||||
msg['telecommand1_text'] = get_telecommand_text(data['telecommand1'])
|
||||
if data.get("telecommand1") is not None:
|
||||
msg["telecommand1"] = data["telecommand1"]
|
||||
msg["telecommand1_text"] = get_telecommand_text(data["telecommand1"])
|
||||
|
||||
if data.get('telecommand2') is not None:
|
||||
msg['telecommand2'] = data['telecommand2']
|
||||
msg['telecommand2_text'] = get_telecommand_text(data['telecommand2'])
|
||||
if data.get("telecommand2") is not None:
|
||||
msg["telecommand2"] = data["telecommand2"]
|
||||
msg["telecommand2_text"] = get_telecommand_text(data["telecommand2"])
|
||||
|
||||
# Add channel if present
|
||||
if data.get('channel') is not None:
|
||||
msg['channel'] = data['channel']
|
||||
if data.get("channel") is not None:
|
||||
msg["channel"] = data["channel"]
|
||||
|
||||
# Add EOS (End of Sequence) info
|
||||
if 'eos' in data:
|
||||
msg['eos'] = data['eos']
|
||||
if "eos" in data:
|
||||
msg["eos"] = data["eos"]
|
||||
|
||||
# Add raw message for debugging
|
||||
if 'raw' in data:
|
||||
msg['raw_message'] = data['raw']
|
||||
if "raw" in data:
|
||||
msg["raw_message"] = data["raw"]
|
||||
|
||||
# Calculate priority
|
||||
msg['priority'] = get_category_priority(msg['category'])
|
||||
msg["priority"] = get_category_priority(msg["category"])
|
||||
|
||||
# Mark if this is a critical alert
|
||||
msg['is_critical'] = msg['category'] in (
|
||||
'DISTRESS', 'DISTRESS_ACK', 'DISTRESS_RELAY',
|
||||
'URGENCY', 'SAFETY', 'ALL_SHIPS_URGENCY_SAFETY',
|
||||
msg["is_critical"] = msg["category"] in (
|
||||
"DISTRESS",
|
||||
"DISTRESS_ACK",
|
||||
"DISTRESS_RELAY",
|
||||
"URGENCY",
|
||||
"SAFETY",
|
||||
"ALL_SHIPS_URGENCY_SAFETY",
|
||||
)
|
||||
|
||||
return msg
|
||||
@@ -269,9 +273,9 @@ def format_dsc_for_display(msg: dict) -> str:
|
||||
lines = []
|
||||
|
||||
# Header with category and MMSI
|
||||
category = msg.get('category', 'UNKNOWN')
|
||||
mmsi = msg.get('source_mmsi', 'UNKNOWN')
|
||||
country = msg.get('source_country', '')
|
||||
category = msg.get("category", "UNKNOWN")
|
||||
mmsi = msg.get("source_mmsi", "UNKNOWN")
|
||||
country = msg.get("source_country", "")
|
||||
|
||||
header = f"[{category}] MMSI: {mmsi}"
|
||||
if country:
|
||||
@@ -279,34 +283,34 @@ def format_dsc_for_display(msg: dict) -> str:
|
||||
lines.append(header)
|
||||
|
||||
# Destination if present
|
||||
if msg.get('dest_mmsi'):
|
||||
if msg.get("dest_mmsi"):
|
||||
lines.append(f" To: {msg['dest_mmsi']}")
|
||||
|
||||
# Distress nature
|
||||
if msg.get('nature_of_distress'):
|
||||
if msg.get("nature_of_distress"):
|
||||
lines.append(f" Nature: {msg['nature_of_distress']}")
|
||||
|
||||
# Position
|
||||
if msg.get('latitude') is not None and msg.get('longitude') is not None:
|
||||
lat = msg['latitude']
|
||||
lon = msg['longitude']
|
||||
lat_dir = 'N' if lat >= 0 else 'S'
|
||||
lon_dir = 'E' if lon >= 0 else 'W'
|
||||
if msg.get("latitude") is not None and msg.get("longitude") is not None:
|
||||
lat = msg["latitude"]
|
||||
lon = msg["longitude"]
|
||||
lat_dir = "N" if lat >= 0 else "S"
|
||||
lon_dir = "E" if lon >= 0 else "W"
|
||||
lines.append(f" Position: {abs(lat):.4f}{lat_dir} {abs(lon):.4f}{lon_dir}")
|
||||
|
||||
# Telecommand
|
||||
if msg.get('telecommand1_text'):
|
||||
if msg.get("telecommand1_text"):
|
||||
lines.append(f" Request: {msg['telecommand1_text']}")
|
||||
|
||||
# Channel
|
||||
if msg.get('channel'):
|
||||
if msg.get("channel"):
|
||||
lines.append(f" Channel: {msg['channel']}")
|
||||
|
||||
# Timestamp
|
||||
if msg.get('timestamp'):
|
||||
if msg.get("timestamp"):
|
||||
lines.append(f" Time: {msg['timestamp']}")
|
||||
|
||||
return '\n'.join(lines)
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def validate_mmsi(mmsi: str) -> bool:
|
||||
@@ -326,11 +330,11 @@ def validate_mmsi(mmsi: str) -> bool:
|
||||
return False
|
||||
|
||||
# Must be 9 digits
|
||||
if not re.match(r'^\d{9}$', mmsi):
|
||||
if not re.match(r"^\d{9}$", mmsi):
|
||||
return False
|
||||
|
||||
# All zeros is invalid
|
||||
return mmsi != '000000000'
|
||||
return mmsi != "000000000"
|
||||
|
||||
|
||||
def classify_mmsi(mmsi: str) -> str:
|
||||
@@ -344,30 +348,30 @@ def classify_mmsi(mmsi: str) -> str:
|
||||
Classification: 'ship', 'coast', 'group', 'sar', 'aton', or 'unknown'
|
||||
"""
|
||||
if not validate_mmsi(mmsi):
|
||||
return 'unknown'
|
||||
return "unknown"
|
||||
|
||||
first_digit = mmsi[0]
|
||||
first_two = mmsi[:2]
|
||||
first_three = mmsi[:3]
|
||||
|
||||
# Coast station: starts with 00
|
||||
if first_two == '00':
|
||||
return 'coast'
|
||||
if first_two == "00":
|
||||
return "coast"
|
||||
|
||||
# Group call: starts with 0
|
||||
if first_digit == '0':
|
||||
return 'group'
|
||||
if first_digit == "0":
|
||||
return "group"
|
||||
|
||||
# SAR aircraft: starts with 111
|
||||
if first_three == '111':
|
||||
return 'sar'
|
||||
if first_three == "111":
|
||||
return "sar"
|
||||
|
||||
# Aids to Navigation: starts with 99
|
||||
if first_two == '99':
|
||||
return 'aton'
|
||||
if first_two == "99":
|
||||
return "aton"
|
||||
|
||||
# Ship station: starts with MID (2-7)
|
||||
if first_digit in '234567':
|
||||
return 'ship'
|
||||
if first_digit in "234567":
|
||||
return "ship"
|
||||
|
||||
return 'unknown'
|
||||
return "unknown"
|
||||
|
||||
Reference in New Issue
Block a user