Devuan fork of gpsd
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1370 lines
52 KiB

  1. #!/usr/bin/env python
  2. #
  3. # A Python AIVDM/AIVDO decoder
  4. #
  5. # This file is Copyright (c) 2010 by the GPSD project
  6. # BSD terms apply: see the file COPYING in the distribution root for details.
  7. #
  8. # This code runs compatibly under Python 2 and 3.x for x >= 2.
  9. # Preserve this property!
  10. from __future__ import absolute_import, print_function, division
  11. from array import array
  12. import exceptions
  13. import re
  14. import sys
  15. try:
  16. BaseException.with_traceback
  17. def reraise_with_traceback(exc_type, exc_value, exc_traceback):
  18. raise exc_type(exc_value).with_traceback(exc_traceback)
  19. except AttributeError:
  20. def reraise_with_traceback(exc_type, exc_value, exc_traceback):
  21. raise exc_type, exc_value, exc_traceback
  22. # This decoder works by defining a declarative pseudolanguage in which
  23. # to describe the process of extracting packed bitfields from an AIS
  24. # message, a set of tables which contain instructions in the pseudolanguage,
  25. # and a small amount of code for interpreting it.
  26. #
  27. # Known bugs:
  28. # * Doesn't join parts A and B of Type 24 together yet.
  29. # * Only handles the broadcast case of type 22. The problem is that the
  30. # addressed field is located *after* the variant parts. Grrrr...
  31. # * Message type 26 is presently unsupported. It hasn't been observed
  32. # in the wild yet as of Jan 2010; not a lot of point in trying util
  33. # we have test data. We'd need new machinery to constrain how many
  34. # bits the data spec eats in order to recover the radio bits after it.
  35. # * No support for IMO236 and IMO289 special messages in types 6 and 8 yet.
  36. #
  37. # Decoding for 1-15, 18-21, and 24 have been tested against live data.
  38. # Decoding for 16-17, 22-23, and 25-27 have not.
  39. # Here are the pseudoinstructions in the pseudolanguage.
  40. class bitfield(object):
  41. "Object defining the interpretation of an AIS bitfield."
  42. # The only un-obvious detail is the use of the oob (out-of-band)
  43. # member. This isn't used in data extraction, but rather to cut
  44. # down on the number of custom formatting hooks. With this we
  45. # handle the case where the field should be reported as an integer
  46. # or "n/a".
  47. def __init__(self, name, width, dtype, oob, legend,
  48. validator=None, formatter=None, conditional=None):
  49. self.name = name # Fieldname, for internal use and JSON
  50. self.width = width # Bit width
  51. self.type = dtype # Data type: signed/unsigned/string/raw
  52. self.oob = oob # Out-of-band value to be shown as n/a
  53. self.legend = legend # Human-friendly description of field
  54. self.validator = validator # Validation checker
  55. self.formatter = formatter # Custom reporting hook.
  56. self.conditional = conditional # Evaluation guard for this field
  57. class spare(object):
  58. "Describes spare bits,, not to be interpreted."
  59. def __init__(self, width, conditional=None):
  60. self.width = width
  61. self.conditional = conditional # Evaluation guard for this field
  62. class dispatch(object):
  63. "Describes how to dispatch to a message type variant on a subfield value."
  64. def __init__(self, fieldname, subtypes, compute=lambda x: x,
  65. conditional=None):
  66. self.fieldname = fieldname # Value of view to dispatch on
  67. self.subtypes = subtypes # Possible subtypes to dispatch to
  68. self.compute = compute # Pass value through this pre-dispatch
  69. self.conditional = conditional # Evaluation guard for this field
  70. # Message-type-specific information begins here. There are four
  71. # different kinds of things in it: (1) string tables for expanding
  72. # enumerated-type codes, (2) hook functions, (3) instruction tables,
  73. # and (4) field group declarations. This is the part that could, in
  74. # theory, be generated from a portable higher-level specification in
  75. # XML; only the hook functions are actually language-specific, and
  76. # your XML definition could in theory embed several different ones for
  77. # code generation in Python, Java, Perl, etc.
  78. cnb_status_legends = (
  79. "Under way using engine",
  80. "At anchor",
  81. "Not under command",
  82. "Restricted manoeuverability",
  83. "Constrained by her draught",
  84. "Moored",
  85. "Aground",
  86. "Engaged in fishing",
  87. "Under way sailing",
  88. "Reserved for HSC",
  89. "Reserved for WIG",
  90. "Reserved",
  91. "Reserved",
  92. "Reserved",
  93. "Reserved",
  94. "Not defined",
  95. )
  96. def cnb_rot_format(n):
  97. if n == -128:
  98. return "n/a"
  99. elif n == -127:
  100. return "fastleft"
  101. elif n == 127:
  102. return "fastright"
  103. else:
  104. return str((n / 4.733) ** 2)
  105. def cnb_latlon_format(n):
  106. return str(n / 600000.0)
  107. def cnb_speed_format(n):
  108. if n == 1023:
  109. return "n/a"
  110. elif n == 1022:
  111. return "fast"
  112. else:
  113. return str(n / 10.0)
  114. def cnb_course_format(n):
  115. return str(n / 10.0)
  116. def cnb_second_format(n):
  117. if n == 60:
  118. return "n/a"
  119. elif n == 61:
  120. return "manual input"
  121. elif n == 62:
  122. return "dead reckoning"
  123. elif n == 63:
  124. return "inoperative"
  125. else:
  126. return str(n)
  127. # Common Navigation Block is the format for AIS types 1, 2, and 3
  128. cnb = (
  129. bitfield("status", 4, 'unsigned', 0, "Navigation Status",
  130. formatter=cnb_status_legends),
  131. bitfield("turn", 8, 'signed', -128, "Rate of Turn",
  132. formatter=cnb_rot_format),
  133. bitfield("speed", 10, 'unsigned', 1023, "Speed Over Ground",
  134. formatter=cnb_speed_format),
  135. bitfield("accuracy", 1, 'unsigned', None, "Position Accuracy"),
  136. bitfield("lon", 28, 'signed', 0x6791AC0, "Longitude",
  137. formatter=cnb_latlon_format),
  138. bitfield("lat", 27, 'signed', 0x3412140, "Latitude",
  139. formatter=cnb_latlon_format),
  140. bitfield("course", 12, 'unsigned', 0xe10, "Course Over Ground",
  141. formatter=cnb_course_format),
  142. bitfield("heading", 9, 'unsigned', 511, "True Heading"),
  143. bitfield("second", 6, 'unsigned', None, "Time Stamp",
  144. formatter=cnb_second_format),
  145. bitfield("maneuver", 2, 'unsigned', None, "Maneuver Indicator"),
  146. spare(3),
  147. bitfield("raim", 1, 'unsigned', None, "RAIM flag"),
  148. bitfield("radio", 19, 'unsigned', None, "Radio status"),
  149. )
  150. epfd_type_legends = (
  151. "Undefined",
  152. "GPS",
  153. "GLONASS",
  154. "Combined GPS/GLONASS",
  155. "Loran-C",
  156. "Chayka",
  157. "Integrated navigation system",
  158. "Surveyed",
  159. "Galileo",
  160. )
  161. type4 = (
  162. bitfield("year", 14, "unsigned", 0, "Year"),
  163. bitfield("month", 4, "unsigned", 0, "Month"),
  164. bitfield("day", 5, "unsigned", 0, "Day"),
  165. bitfield("hour", 5, "unsigned", 24, "Hour"),
  166. bitfield("minute", 6, "unsigned", 60, "Minute"),
  167. bitfield("second", 6, "unsigned", 60, "Second"),
  168. bitfield("accuracy", 1, "unsigned", None, "Fix quality"),
  169. bitfield("lon", 28, "signed", 0x6791AC0, "Longitude",
  170. formatter=cnb_latlon_format),
  171. bitfield("lat", 27, "signed", 0x3412140, "Latitude",
  172. formatter=cnb_latlon_format),
  173. bitfield("epfd", 4, "unsigned", None, "Type of EPFD",
  174. validator=lambda n: n >= 0 and n <= 8 or n == 15,
  175. formatter=epfd_type_legends),
  176. spare(10),
  177. bitfield("raim", 1, "unsigned", None, "RAIM flag "),
  178. bitfield("radio", 19, "unsigned", None, "SOTDMA state"),
  179. )
  180. ship_type_legends = (
  181. "Not available",
  182. "Reserved for future use",
  183. "Reserved for future use",
  184. "Reserved for future use",
  185. "Reserved for future use",
  186. "Reserved for future use",
  187. "Reserved for future use",
  188. "Reserved for future use",
  189. "Reserved for future use",
  190. "Reserved for future use",
  191. "Reserved for future use",
  192. "Reserved for future use",
  193. "Reserved for future use",
  194. "Reserved for future use",
  195. "Reserved for future use",
  196. "Reserved for future use",
  197. "Reserved for future use",
  198. "Reserved for future use",
  199. "Reserved for future use",
  200. "Reserved for future use",
  201. "Wing in ground (WIG) - all ships of this type",
  202. "Wing in ground (WIG) - Hazardous category A",
  203. "Wing in ground (WIG) - Hazardous category B",
  204. "Wing in ground (WIG) - Hazardous category C",
  205. "Wing in ground (WIG) - Hazardous category D",
  206. "Wing in ground (WIG) - Reserved for future use",
  207. "Wing in ground (WIG) - Reserved for future use",
  208. "Wing in ground (WIG) - Reserved for future use",
  209. "Wing in ground (WIG) - Reserved for future use",
  210. "Wing in ground (WIG) - Reserved for future use",
  211. "Fishing",
  212. "Towing",
  213. "Towing: length exceeds 200m or breadth exceeds 25m",
  214. "Dredging or underwater ops",
  215. "Diving ops",
  216. "Military ops",
  217. "Sailing",
  218. "Pleasure Craft",
  219. "Reserved",
  220. "Reserved",
  221. "High speed craft (HSC) - all ships of this type",
  222. "High speed craft (HSC) - Hazardous category A",
  223. "High speed craft (HSC) - Hazardous category B",
  224. "High speed craft (HSC) - Hazardous category C",
  225. "High speed craft (HSC) - Hazardous category D",
  226. "High speed craft (HSC) - Reserved for future use",
  227. "High speed craft (HSC) - Reserved for future use",
  228. "High speed craft (HSC) - Reserved for future use",
  229. "High speed craft (HSC) - Reserved for future use",
  230. "High speed craft (HSC) - No additional information",
  231. "Pilot Vessel",
  232. "Search and Rescue vessel",
  233. "Tug",
  234. "Port Tender",
  235. "Anti-pollution equipment",
  236. "Law Enforcement",
  237. "Spare - Local Vessel",
  238. "Spare - Local Vessel",
  239. "Medical Transport",
  240. "Ship according to RR Resolution No. 18",
  241. "Passenger - all ships of this type",
  242. "Passenger - Hazardous category A",
  243. "Passenger - Hazardous category B",
  244. "Passenger - Hazardous category C",
  245. "Passenger - Hazardous category D",
  246. "Passenger - Reserved for future use",
  247. "Passenger - Reserved for future use",
  248. "Passenger - Reserved for future use",
  249. "Passenger - Reserved for future use",
  250. "Passenger - No additional information",
  251. "Cargo - all ships of this type",
  252. "Cargo - Hazardous category A",
  253. "Cargo - Hazardous category B",
  254. "Cargo - Hazardous category C",
  255. "Cargo - Hazardous category D",
  256. "Cargo - Reserved for future use",
  257. "Cargo - Reserved for future use",
  258. "Cargo - Reserved for future use",
  259. "Cargo - Reserved for future use",
  260. "Cargo - No additional information",
  261. "Tanker - all ships of this type",
  262. "Tanker - Hazardous category A",
  263. "Tanker - Hazardous category B",
  264. "Tanker - Hazardous category C",
  265. "Tanker - Hazardous category D",
  266. "Tanker - Reserved for future use",
  267. "Tanker - Reserved for future use",
  268. "Tanker - Reserved for future use",
  269. "Tanker - Reserved for future use",
  270. "Tanker - No additional information",
  271. "Other Type - all ships of this type",
  272. "Other Type - Hazardous category A",
  273. "Other Type - Hazardous category B",
  274. "Other Type - Hazardous category C",
  275. "Other Type - Hazardous category D",
  276. "Other Type - Reserved for future use",
  277. "Other Type - Reserved for future use",
  278. "Other Type - Reserved for future use",
  279. "Other Type - Reserved for future use",
  280. "Other Type - no additional information",
  281. )
  282. type5 = (
  283. bitfield("ais_version", 2, 'unsigned', None, "AIS Version"),
  284. bitfield("imo_id", 30, 'unsigned', 0,
  285. "IMO Identification Number"),
  286. bitfield("callsign", 42, 'string', None, "Call Sign"),
  287. bitfield("shipname", 120, 'string', None, "Vessel Name"),
  288. bitfield("shiptype", 8, 'unsigned', None, "Ship Type",
  289. # validator=lambda n: n >= 0 and n <= 99,
  290. formatter=ship_type_legends),
  291. bitfield("to_bow", 9, 'unsigned', 0, "Dimension to Bow"),
  292. bitfield("to_stern", 9, 'unsigned', 0, "Dimension to Stern"),
  293. bitfield("to_port", 6, 'unsigned', 0, "Dimension to Port"),
  294. bitfield("to_starbord", 6, 'unsigned', 0, "Dimension to Starboard"),
  295. bitfield("epfd", 4, 'unsigned', 0, "Position Fix Type",
  296. validator=lambda n: n >= 0 and n <= 8 or n == 15,
  297. formatter=epfd_type_legends),
  298. bitfield("month", 4, 'unsigned', 0, "ETA month"),
  299. bitfield("day", 5, 'unsigned', 0, "ETA day"),
  300. bitfield("hour", 5, 'unsigned', 24, "ETA hour"),
  301. bitfield("minute", 6, 'unsigned', 60, "ETA minute"),
  302. bitfield("draught", 8, 'unsigned', 0, "Draught",
  303. formatter=lambda n: n/10.0),
  304. bitfield("destination", 120, 'string', None, "Destination"),
  305. bitfield("dte", 1, 'unsigned', None, "DTE"),
  306. spare(1),
  307. )
  308. type6_dac_or_fid_unknown = (
  309. bitfield("data", 920, 'raw', None, "Data"),
  310. )
  311. type6_dispatch = {}
  312. type6_dispatch[0] = type6_dac_or_fid_unknown
  313. # DAC 235 and 250 (UK, Rep. of Ireland)
  314. type6_dac235_dispatch = {}
  315. type6_dac235_dispatch[0] = type6_dac_or_fid_unknown
  316. type6_dac235_fid10 = (
  317. bitfield("ana_int", 10, 'unsigned', None, "Supply voltage"),
  318. bitfield("ana_ext1", 10, 'unsigned', None, "Analogue (Ext#1)"),
  319. bitfield("ana_ext2", 10, 'unsigned', None, "Analogue (Ext#2)"),
  320. bitfield("racon", 2, 'unsigned', None, "RACON status"),
  321. bitfield("light", 2, 'unsigned', None, "Light status"),
  322. bitfield("health", 1, 'unsigned', None, "Health"),
  323. bitfield("stat_ext", 8, 'unsigned', None, "Status (ext)"),
  324. bitfield("off_pos", 1, 'unsigned', None, "Position status"),
  325. )
  326. type6_dac235_dispatch[10] = type6_dac235_fid10
  327. type6_dac235 = (
  328. dispatch("fid", type6_dac235_dispatch,
  329. lambda m: m if m in type6_dac235_dispatch else 0),
  330. )
  331. type6_dispatch[235] = type6_dac235
  332. type6_dispatch[250] = type6_dac235
  333. type6 = (
  334. bitfield("seqno", 2, 'unsigned', None, "Sequence Number"),
  335. bitfield("dest_mmsi", 30, 'unsigned', None, "Destination MMSI"),
  336. bitfield("retransmit", 1, 'unsigned', None, "Retransmit flag"),
  337. spare(1),
  338. bitfield("dac", 10, 'unsigned', 0, "DAC"),
  339. bitfield("fid", 6, 'unsigned', 0, "Functional ID"),
  340. dispatch("dac", type6_dispatch, lambda m: m if m in type6_dispatch else 0),
  341. )
  342. type7 = (
  343. spare(2),
  344. bitfield("mmsi1", 30, 'unsigned', 0, "MMSI number 1"),
  345. spare(2),
  346. bitfield("mmsi2", 30, 'unsigned', 0, "MMSI number 2"),
  347. spare(2),
  348. bitfield("mmsi3", 30, 'unsigned', 0, "MMSI number 3"),
  349. spare(2),
  350. bitfield("mmsi1", 30, 'unsigned', 0, "MMSI number 4"),
  351. spare(2),
  352. )
  353. #
  354. # Type 8 have subtypes identified by DAC (Designated Area Code) and
  355. # FID (Functional ID)
  356. #
  357. def type8_latlon_format(n):
  358. return str(n / 60000.0)
  359. type8_dac_or_fid_unknown = (
  360. bitfield("data", 952, 'raw', None, "Data"),
  361. )
  362. type8_dispatch = {}
  363. type8_dispatch[0] = type8_dac_or_fid_unknown
  364. # DAC 1 (international)
  365. type8_dac1_dispatch = {}
  366. type8_dac1_dispatch[0] = type8_dac_or_fid_unknown
  367. # DAC 1, FID 11: IMO236 Met/Hydro message
  368. def type8_dac1_fid11_airtemp_format(n):
  369. return str(n * 0.1 - 60)
  370. def type8_dac1_fid11_dewpoint_format(n):
  371. return str(n * 0.1 - 20)
  372. def type8_dac1_fid11_pressure_format(n):
  373. return str(n + 800)
  374. def type8_dac1_fid11_visibility_format(n):
  375. return str(n * 0.1)
  376. def type8_dac1_fid11_waterlevel_format(n):
  377. return str(n * 0.1 - 10)
  378. def type8_dac1_fid11_cspeed_format(n):
  379. return str(n * 0.1)
  380. def type8_dac1_fid11_waveheight_format(n):
  381. return str(n * 0.1)
  382. type8_dac1_fid11_seastate_legend = (
  383. "Calm",
  384. "Light air",
  385. "Light breeze"
  386. "Gentle breeze",
  387. "Moderate breeze",
  388. "Fresh breeze",
  389. "Strong breeze",
  390. "High wind",
  391. "Gale",
  392. "Strong gale",
  393. "Storm",
  394. "Violent storm",
  395. "Hurricane force",
  396. "Reserved",
  397. "Reserved",
  398. "Reserved"
  399. )
  400. def type8_dac1_fid11_watertemp_format(n):
  401. return str(n * 0.1 - 10)
  402. type8_dac1_fid11_preciptype_legend = (
  403. "Reserved",
  404. "Rain",
  405. "Thunderstorm",
  406. "Freezing rain",
  407. "Mixed/ice",
  408. "Snow",
  409. "Reserved",
  410. "Reserved"
  411. )
  412. def type8_dac1_fid11_salinity_format(n):
  413. return str(n * 0.1)
  414. type8_dac1_fid11_ice_legend = (
  415. "Yes",
  416. "No"
  417. )
  418. type8_dac1_fid11 = (
  419. bitfield("lat", 24, "signed", 2**24-1, "Latitude",
  420. formatter=type8_latlon_format),
  421. bitfield("lon", 25, "signed", 2**25-1, "Longitude",
  422. formatter=type8_latlon_format),
  423. bitfield("day", 5, 'unsigned', 0, "ETA day"),
  424. bitfield("hour", 5, 'unsigned', 24, "ETA hour"),
  425. bitfield("minute", 6, 'unsigned', 60, "ETA minute"),
  426. bitfield("wspeed", 7, 'unsigned', 127, "Wind speed"),
  427. bitfield("wgust", 7, 'unsigned', 127, "Wind gust"),
  428. bitfield("wdir", 9, 'unsigned', 511, "Wind direction"),
  429. bitfield("wgustdir", 9, 'unsigned', 511, "Wind gust direction"),
  430. bitfield("airtemp", 11, 'unsigned', 2047, "Air temperature",
  431. formatter=type8_dac1_fid11_airtemp_format),
  432. bitfield("humidity", 7, 'unsigned', 127, "Relative humidity"),
  433. bitfield("dewpoint", 10, 'unsigned', 1023, "Dew point",
  434. formatter=type8_dac1_fid11_dewpoint_format),
  435. bitfield("pressure", 9, 'unsigned', 511, "Atmospheric pressure",
  436. formatter=type8_dac1_fid11_pressure_format),
  437. bitfield("pressuretend", 2, 'unsigned', 3,
  438. "Atmospheric pressure tendency"),
  439. bitfield("visibility", 8, 'unsigned', 255, "Horizontal visibility",
  440. formatter=type8_dac1_fid11_visibility_format),
  441. bitfield("waterlevel", 9, 'unsigned', 511, "Water level",
  442. formatter=type8_dac1_fid11_waterlevel_format),
  443. bitfield("leveltrend", 2, 'unsigned', 3, "Water level trend"),
  444. bitfield("cspeed", 8, 'unsigned', 255, "Surface current speed",
  445. formatter=type8_dac1_fid11_cspeed_format),
  446. bitfield("cdir", 9, 'unsigned', 511,
  447. "Surface current direction"),
  448. bitfield("cspeed2", 8, 'unsigned', 255, "Current speed #2",
  449. formatter=type8_dac1_fid11_cspeed_format),
  450. bitfield("cdir2", 9, 'unsigned', 511, "Current direction #2"),
  451. bitfield("cdepth2", 5, 'unsigned', 31,
  452. "Current measuring level #2"),
  453. bitfield("cspeed3", 8, 'unsigned', 255, "Current speed #3",
  454. formatter=type8_dac1_fid11_cspeed_format),
  455. bitfield("cdir3", 9, 'unsigned', 511, "Current direction #3"),
  456. bitfield("cdepth3", 5, 'unsigned', 31,
  457. "Current measuring level #3"),
  458. bitfield("waveheight", 8, 'unsigned', 255, "Significant wave height",
  459. formatter=type8_dac1_fid11_waveheight_format),
  460. bitfield("waveperiod", 6, 'unsigned', 63, "Significant wave period"),
  461. bitfield("wavedir", 9, 'unsigned', 511,
  462. "Significant wave direction"),
  463. bitfield("swellheight", 8, 'unsigned', 255, "Swell height",
  464. formatter=type8_dac1_fid11_waveheight_format),
  465. bitfield("swellperiod", 6, 'unsigned', 63, "Swell period"),
  466. bitfield("swelldir", 9, 'unsigned', 511, "Swell direction"),
  467. bitfield("seastate", 4, 'unsigned', 15, "Sea state",
  468. formatter=type8_dac1_fid11_seastate_legend),
  469. bitfield("watertemp", 10, 'unsigned', 1023, "Water temperature",
  470. formatter=type8_dac1_fid11_watertemp_format),
  471. bitfield("preciptype", 3, 'unsigned', 7, "Precipitation type",
  472. formatter=type8_dac1_fid11_preciptype_legend),
  473. bitfield("salinity", 9, 'unsigned', 511, "Salinity",
  474. formatter=type8_dac1_fid11_salinity_format),
  475. bitfield("ice", 2, 'unsigned', 3, "Ice?",
  476. formatter=type8_dac1_fid11_ice_legend),
  477. spare(6)
  478. )
  479. type8_dac1_dispatch[11] = type8_dac1_fid11
  480. type8_dac1 = (
  481. dispatch("fid", type8_dac1_dispatch,
  482. lambda m: m if m in type8_dac1_dispatch else 0),
  483. )
  484. type8_dispatch[1] = type8_dac1
  485. type8 = (
  486. spare(2),
  487. bitfield("dac", 10, 'unsigned', 0, "DAC"),
  488. bitfield("fid", 6, 'unsigned', 0, "Functional ID"),
  489. dispatch("dac", type8_dispatch, lambda m: m if m in type8_dispatch else 0),
  490. )
  491. def type9_alt_format(n):
  492. if n == 4094:
  493. return ">=4094"
  494. else:
  495. return str(n)
  496. def type9_speed_format(n):
  497. if n == 1023:
  498. return "n/a"
  499. elif n == 1022:
  500. return "fast"
  501. else:
  502. return str(n)
  503. type9 = (
  504. bitfield("alt", 12, 'unsigned', 4095, "Altitude",
  505. formatter=type9_alt_format),
  506. bitfield("speed", 10, 'unsigned', 1023, "SOG",
  507. formatter=type9_speed_format),
  508. bitfield("accuracy", 1, 'unsigned', None, "Position Accuracy"),
  509. bitfield("lon", 28, 'signed', 0x6791AC0, "Longitude",
  510. formatter=cnb_latlon_format),
  511. bitfield("lat", 27, 'signed', 0x3412140, "Latitude",
  512. formatter=cnb_latlon_format),
  513. bitfield("course", 12, 'unsigned', 0xe10, "Course Over Ground",
  514. formatter=cnb_course_format),
  515. bitfield("second", 6, 'unsigned', 60, "Time Stamp",
  516. formatter=cnb_second_format),
  517. bitfield("regional", 8, 'unsigned', None, "Regional reserved"),
  518. bitfield("dte", 1, 'unsigned', None, "DTE"),
  519. spare(3),
  520. bitfield("assigned", 1, 'unsigned', None, "Assigned"),
  521. bitfield("raim", 1, 'unsigned', None, "RAIM flag"),
  522. bitfield("radio", 19, 'unsigned', None, "Radio status"),
  523. )
  524. type10 = (
  525. spare(2),
  526. bitfield("dest_mmsi", 30, 'unsigned', None, "Destination MMSI"),
  527. spare(2),
  528. )
  529. type12 = (
  530. bitfield("seqno", 2, 'unsigned', None, "Sequence Number"),
  531. bitfield("dest_mmsi", 30, 'unsigned', None, "Destination MMSI"),
  532. bitfield("retransmit", 1, 'unsigned', None, "Retransmit flag"),
  533. spare(1),
  534. bitfield("text", 936, 'string', None, "Text"),
  535. )
  536. type14 = (
  537. spare(2),
  538. bitfield("text", 968, 'string', None, "Text"),
  539. )
  540. type15 = (
  541. spare(2),
  542. bitfield("mmsi1", 30, 'unsigned', 0, "First interrogated MMSI"),
  543. bitfield("type1_1", 6, 'unsigned', 0, "First message type"),
  544. bitfield("offset1_1", 12, 'unsigned', 0, "First slot offset"),
  545. spare(2),
  546. bitfield("type1_2", 6, 'unsigned', 0, "Second message type"),
  547. bitfield("offset1_2", 12, 'unsigned', 0, "Second slot offset"),
  548. spare(2),
  549. bitfield("mmsi2", 30, 'unsigned', 0, "Second interrogated MMSI"),
  550. bitfield("type2_1", 6, 'unsigned', 0, "Message type"),
  551. bitfield("offset2_1", 12, 'unsifned', 0, "Slot offset"),
  552. spare(2),
  553. )
  554. type16 = (
  555. spare(2),
  556. bitfield("mmsi1", 30, 'unsigned', 0, "Interrogated MMSI 1"),
  557. bitfield("offset1", 12, 'unsigned', 0, "First slot offset"),
  558. bitfield("increment1", 10, 'unsigned', 0, "First slot increment"),
  559. bitfield("mmsi2", 30, 'unsigned', 0, "Interrogated MMSI 2"),
  560. bitfield("offset2", 12, 'unsigned', 0, "Second slot offset"),
  561. bitfield("increment2", 10, 'unsigned', 0, "Second slot increment"),
  562. spare(2),
  563. )
  564. def short_latlon_format(n):
  565. return str(n / 600.0)
  566. type17 = (
  567. spare(2),
  568. bitfield("lon", 18, 'signed', 0x1a838, "Longitude",
  569. formatter=short_latlon_format),
  570. bitfield("lat", 17, 'signed', 0xd548, "Latitude",
  571. formatter=short_latlon_format),
  572. spare(5),
  573. bitfield("data", 736, 'raw', None, "DGNSS data"),
  574. )
  575. type18 = (
  576. bitfield("reserved", 8, 'unsigned', None, "Regional reserved"),
  577. bitfield("speed", 10, 'unsigned', 1023, "Speed Over Ground",
  578. formatter=cnb_speed_format),
  579. bitfield("accuracy", 1, 'unsigned', None, "Position Accuracy"),
  580. bitfield("lon", 28, 'signed', 0x6791AC0, "Longitude",
  581. formatter=cnb_latlon_format),
  582. bitfield("lat", 27, 'signed', 0x3412140, "Latitude",
  583. formatter=cnb_latlon_format),
  584. bitfield("course", 12, 'unsigned', 0xE10, "Course Over Ground",
  585. formatter=cnb_course_format),
  586. bitfield("heading", 9, 'unsigned', 511, "True Heading"),
  587. bitfield("second", 6, 'unsigned', None, "Time Stamp",
  588. formatter=cnb_second_format),
  589. bitfield("regional", 2, 'unsigned', None, "Regional reserved"),
  590. bitfield("cs", 1, 'unsigned', None, "CS Unit"),
  591. bitfield("display", 1, 'unsigned', None, "Display flag"),
  592. bitfield("dsc", 1, 'unsigned', None, "DSC flag"),
  593. bitfield("band", 1, 'unsigned', None, "Band flag"),
  594. bitfield("msg22", 1, 'unsigned', None, "Message 22 flag"),
  595. bitfield("assigned", 1, 'unsigned', None, "Assigned"),
  596. bitfield("raim", 1, 'unsigned', None, "RAIM flag"),
  597. bitfield("radio", 20, 'unsigned', None, "Radio status"),
  598. )
  599. type19 = (
  600. bitfield("reserved", 8, 'unsigned', None, "Regional reserved"),
  601. bitfield("speed", 10, 'unsigned', 1023, "Speed Over Ground",
  602. formatter=cnb_speed_format),
  603. bitfield("accuracy", 1, 'unsigned', None, "Position Accuracy"),
  604. bitfield("lon", 28, 'signed', 0x6791AC0, "Longitude",
  605. formatter=cnb_latlon_format),
  606. bitfield("lat", 27, 'signed', 0x3412140, "Latitude",
  607. formatter=cnb_latlon_format),
  608. bitfield("course", 12, 'unsigned', 0xE10, "Course Over Ground",
  609. formatter=cnb_course_format),
  610. bitfield("heading", 9, 'unsigned', 511, "True Heading"),
  611. bitfield("second", 6, 'unsigned', None, "Time Stamp",
  612. formatter=cnb_second_format),
  613. bitfield("regional", 4, 'unsigned', None, "Regional reserved"),
  614. bitfield("shipname", 120, 'string', None, "Vessel Name"),
  615. bitfield("shiptype", 8, 'unsigned', None, "Ship Type",
  616. # validator=lambda n: n >= 0 and n <= 99,
  617. formatter=ship_type_legends),
  618. bitfield("to_bow", 9, 'unsigned', 0, "Dimension to Bow"),
  619. bitfield("to_stern", 9, 'unsigned', 0, "Dimension to Stern"),
  620. bitfield("to_port", 6, 'unsigned', 0, "Dimension to Port"),
  621. bitfield("to_starbord", 6, 'unsigned', 0, "Dimension to Starboard"),
  622. bitfield("epfd", 4, 'unsigned', 0, "Position Fix Type",
  623. validator=lambda n: n >= 0 and n <= 8 or n == 15,
  624. formatter=epfd_type_legends),
  625. bitfield("assigned", 1, 'unsigned', None, "Assigned"),
  626. bitfield("raim", 1, 'unsigned', None, "RAIM flag"),
  627. bitfield("radio", 20, 'unsigned', None, "Radio status"),
  628. )
  629. type20 = (
  630. spare(2),
  631. bitfield("offset1", 12, 'unsigned', 0, "Offset number"),
  632. bitfield("number1", 4, 'unsigned', 0, "Reserved slots"),
  633. bitfield("timeout1", 3, 'unsigned', 0, "Time-out"),
  634. bitfield("increment1", 11, 'unsigned', 0, "Increment"),
  635. bitfield("offset2", 12, 'unsigned', 0, "Offset number 2"),
  636. bitfield("number2", 4, 'unsigned', 0, "Reserved slots"),
  637. bitfield("timeout2", 3, 'unsigned', 0, "Time-out"),
  638. bitfield("increment2", 11, 'unsigned', 0, "Increment"),
  639. bitfield("offset3", 12, 'unsigned', 0, "Offset number 3"),
  640. bitfield("number3", 4, 'unsigned', 0, "Reserved slots"),
  641. bitfield("timeout3", 3, 'unsigned', 0, "Time-out"),
  642. bitfield("increment3", 11, 'unsigned', 0, "Increment"),
  643. bitfield("offset4", 12, 'unsigned', 0, "Offset number 4"),
  644. bitfield("number4", 4, 'unsigned', 0, "Reserved slots"),
  645. bitfield("timeout4", 3, 'unsigned', 0, "Time-out"),
  646. bitfield("increment4", 11, 'unsigned', 0, "Increment"),
  647. )
  648. aide_type_legends = (
  649. "Unspecified",
  650. "Reference point",
  651. "RACON",
  652. "Fixed offshore structure",
  653. "Spare, Reserved for future use.",
  654. "Light, without sectors",
  655. "Light, with sectors",
  656. "Leading Light Front",
  657. "Leading Light Rear",
  658. "Beacon, Cardinal N",
  659. "Beacon, Cardinal E",
  660. "Beacon, Cardinal S",
  661. "Beacon, Cardinal W",
  662. "Beacon, Port hand",
  663. "Beacon, Starboard hand",
  664. "Beacon, Preferred Channel port hand",
  665. "Beacon, Preferred Channel starboard hand",
  666. "Beacon, Isolated danger",
  667. "Beacon, Safe water",
  668. "Beacon, Special mark",
  669. "Cardinal Mark N",
  670. "Cardinal Mark E",
  671. "Cardinal Mark S",
  672. "Cardinal Mark W",
  673. "Port hand Mark",
  674. "Starboard hand Mark",
  675. "Preferred Channel Port hand",
  676. "Preferred Channel Starboard hand",
  677. "Isolated danger",
  678. "Safe Water",
  679. "Special Mark",
  680. "Light Vessel / LANBY / Rigs",
  681. )
  682. type21 = (
  683. bitfield("aid_type", 5, 'unsigned', 0, "Aid type",
  684. formatter=aide_type_legends),
  685. bitfield("name", 120, 'string', None, "Name"),
  686. bitfield("accuracy", 1, 'unsigned', 0, "Position Accuracy"),
  687. bitfield("lon", 28, 'signed', 0x6791AC0, "Longitude",
  688. formatter=cnb_latlon_format),
  689. bitfield("lat", 27, 'signed', 0x3412140, "Latitude",
  690. formatter=cnb_latlon_format),
  691. bitfield("to_bow", 9, 'unsigned', 0, "Dimension to Bow"),
  692. bitfield("to_stern", 9, 'unsigned', 0, "Dimension to Stern"),
  693. bitfield("to_port", 6, 'unsigned', 0, "Dimension to Port"),
  694. bitfield("to_starboard", 6, 'unsigned', 0, "Dimension to Starboard"),
  695. bitfield("epfd", 4, 'unsigned', 0, "Position Fix Type",
  696. validator=lambda n: n >= 0 and n <= 8 or n == 15,
  697. formatter=epfd_type_legends),
  698. bitfield("second", 6, 'unsigned', 0, "UTC Second"),
  699. bitfield("off_position", 1, 'unsigned', 0, "Off-Position Indicator"),
  700. bitfield("regional", 8, 'unsigned', 0, "Regional reserved"),
  701. bitfield("raim", 1, 'unsigned', 0, "RAIM flag"),
  702. bitfield("virtual_aid", 1, 'unsigned', 0, "Virtual-aid flag"),
  703. bitfield("assigned", 1, 'unsigned', 0, "Assigned-mode flag"),
  704. spare(1),
  705. bitfield("name", 88, 'string', 0, "Name Extension"),
  706. )
  707. type22 = (
  708. spare(2),
  709. bitfield("channel_a", 12, 'unsigned', 0, "Channel A"),
  710. bitfield("channel_b", 12, 'unsigned', 0, "Channel B"),
  711. bitfield("txrx", 4, 'unsigned', 0, "Tx/Rx mode"),
  712. bitfield("power", 1, 'unsigned', 0, "Power"),
  713. bitfield("ne_lon", 18, 'signed', 0x1a838, "NE Longitude",
  714. formatter=short_latlon_format),
  715. bitfield("ne_lat", 17, 'signed', 0xd548, "NE Latitude",
  716. formatter=short_latlon_format),
  717. bitfield("sw_lon", 18, 'signed', 0x1a838, "SW Longitude",
  718. formatter=short_latlon_format),
  719. bitfield("sw_lat", 17, 'signed', 0xd548, "SW Latitude",
  720. formatter=short_latlon_format),
  721. bitfield("addressed", 1, 'unsigned', 0, "Addressed"),
  722. bitfield("band_a", 1, 'unsigned', 0, "Channel A Band"),
  723. bitfield("band_a", 1, 'unsigned', 0, "Channel A Band"),
  724. bitfield("zonesize", 3, 'unsigned', 0, "Zone size"),
  725. spare(23),
  726. )
  727. station_type_legends = (
  728. "All types of mobiles",
  729. "Reserved for future use",
  730. "All types of Class B mobile stations",
  731. "SAR airborne mobile station",
  732. "Aid to Navigation station",
  733. "Class B shipborne mobile station",
  734. "Regional use and inland waterways",
  735. "Regional use and inland waterways",
  736. "Regional use and inland waterways",
  737. "Regional use and inland waterways",
  738. "Reserved for future use",
  739. "Reserved for future use",
  740. "Reserved for future use",
  741. "Reserved for future use",
  742. "Reserved for future use",
  743. "Reserved for future use",
  744. )
  745. type23 = (
  746. spare(2),
  747. bitfield("ne_lon", 18, 'signed', 0x1a838, "NE Longitude",
  748. formatter=short_latlon_format),
  749. bitfield("ne_lat", 17, 'signed', 0xd548, "NE Latitude",
  750. formatter=short_latlon_format),
  751. bitfield("sw_lon", 18, 'signed', 0x1a838, "SW Longitude",
  752. formatter=short_latlon_format),
  753. bitfield("sw_lat", 17, 'signed', 0xd548, "SW Latitude",
  754. formatter=short_latlon_format),
  755. bitfield("stationtype", 4, 'unsigned', 0, "Station Type",
  756. validator=lambda n: n >= 0 and n <= 31,
  757. formatter=station_type_legends),
  758. bitfield("shiptype", 8, 'unsigned', 0, "Ship Type",
  759. # validator=lambda n: n >= 0 and n <= 99,
  760. formatter=ship_type_legends),
  761. spare(22),
  762. bitfield("txrx", 2, 'unsigned', 0, "Tx/Rx mode"),
  763. bitfield("interval", 4, 'unsigned', 0, "Reporting interval"),
  764. bitfield("txrx", 4, 'unsigned', 0, "Quiet time"),
  765. )
  766. type24a = (
  767. bitfield("shipname", 120, 'string', None, "Vessel Name"),
  768. spare(8),
  769. )
  770. type24b1 = (
  771. bitfield("callsign", 42, 'string', None, "Call Sign"),
  772. bitfield("to_bow", 9, 'unsigned', 0, "Dimension to Bow"),
  773. bitfield("to_stern", 9, 'unsigned', 0, "Dimension to Stern"),
  774. bitfield("to_port", 6, 'unsigned', 0, "Dimension to Port"),
  775. bitfield("to_starbord", 6, 'unsigned', 0, "Dimension to Starboard"),
  776. spare(8),
  777. )
  778. type24b2 = (
  779. bitfield('mothership_mmsi', 30, 'unsigned', 0, "Mothership MMSI"),
  780. spare(8),
  781. )
  782. type24b = (
  783. bitfield("shiptype", 8, 'unsigned', None, "Ship Type",
  784. validator=lambda n: n >= 0 and n <= 99,
  785. formatter=ship_type_legends),
  786. bitfield("vendorid", 42, 'string', None, "Vendor ID"),
  787. dispatch("mmsi", {0: type24b1, 1: type24b2},
  788. lambda m: 1 if repr(m)[:2] == '98' else 0),
  789. )
  790. type24 = (
  791. bitfield('partno', 2, 'unsigned', None, "Part Number"),
  792. dispatch('partno', {0: type24a, 1: type24b}),
  793. )
  794. type25 = (
  795. bitfield("addressed", 1, 'unsigned', None, "Addressing flag"),
  796. bitfield("structured", 1, 'unsigned', None, "Dimension to Bow"),
  797. bitfield("dest_mmsi", 30, 'unsigned', 0, "Destinstion MMSI",
  798. conditional=lambda i, v: v["addressed"]),
  799. bitfield("app_id", 16, 'unsigned', 0, "Application ID",
  800. conditional=lambda i, v: v["structured"]),
  801. bitfield("data", 0, 'raw', None, "Data"),
  802. )
  803. # No type 26 handling yet,
  804. type27 = (
  805. bitfield("accuracy", 1, 'unsigned', None, "Position Accuracy"),
  806. bitfield("raim", 1, 'unsigned', None, "RAIM flag"),
  807. bitfield("status", 4, 'unsigned', 0, "Navigation Status",
  808. formatter=cnb_status_legends),
  809. bitfield("lon", 18, 'signed', 0x1a838, "Longitude",
  810. formatter=short_latlon_format),
  811. bitfield("lat", 17, 'signed', 0xd548, "Latitude",
  812. formatter=short_latlon_format),
  813. bitfield("speed", 6, 'unsigned', 63, "Speed Over Ground",
  814. formatter=cnb_speed_format),
  815. bitfield("course", 9, 'unsigned', 511, "Course Over Ground"),
  816. bitfield("GNSS", 1, 'unsigned', None, "GNSS flag"),
  817. spare(1),
  818. )
  819. aivdm_decode = (
  820. bitfield('msgtype', 6, 'unsigned', 0, "Message Type",
  821. validator=lambda n: n > 0 and n <= 27),
  822. bitfield('repeat', 2, 'unsigned', None, "Repeat Indicator"),
  823. bitfield('mmsi', 30, 'unsigned', 0, "MMSI"),
  824. # This is the master dispatch on AIS message type
  825. dispatch('msgtype', {0: None, 1: cnb, 2: cnb, 3: cnb,
  826. 4: type4, 5: type5, 6: type6, 7: type7,
  827. 8: type8, 9: type9, 10: type10, 11: type4,
  828. 12: type12, 13: type7, 14: type14, 15: type15,
  829. 16: type16, 17: type17, 18: type18, 19: type19,
  830. 20: type20, 21: type21, 22: type22, 23: type23,
  831. 24: type24, 25: type25, 26: None, 27: type27}),
  832. )
  833. # Length ranges. We use this for integrity checking.
  834. # When a range is a tuple, it's (minimum, maximum).
  835. lengths = {
  836. 1: 168,
  837. 2: 168,
  838. 3: 168,
  839. 4: 168,
  840. 5: 424,
  841. 6: (88, 1008),
  842. 7: (72, 168),
  843. 8: (56, 1008),
  844. 9: 168,
  845. 10: 72,
  846. 11: 168,
  847. 12: (72, 1008),
  848. 13: (72, 168),
  849. 14: (40, 1008),
  850. 15: (88, 168),
  851. 16: (96, 144),
  852. 17: (80, 816),
  853. 18: 168,
  854. 19: 312,
  855. 20: (72, 160),
  856. 21: (272, 368),
  857. 22: 168,
  858. 23: 160,
  859. 24: (160, 168),
  860. 25: 168,
  861. 26: (60, 1004),
  862. 27: 96,
  863. }
  864. field_groups = (
  865. # This one occurs in message type 4
  866. (3, ["year", "month", "day", "hour", "minute", "second"],
  867. "time", "Timestamp",
  868. lambda y, m, d, h, n, s: "%02d-%02d-%02dT%02d:%02d:%02dZ" %
  869. (y, m, d, h, n, s)),
  870. # This one is in message 5
  871. (13, ["month", "day", "hour", "minute", "second"],
  872. "eta", "Estimated Time of Arrival",
  873. lambda m, d, h, n, s: "%02d-%02dT%02d:%02d:%02dZ" % (m, d, h, n, s)),
  874. )
  875. # Message-type-specific information ends here.
  876. #
  877. # Next, the execution machinery for the pseudolanguage. There isn't much of
  878. # this: the whole point of the design is to embody most of the information
  879. # about the AIS format in the pseudoinstruction tables.
  880. BITS_PER_BYTE = 8
  881. class BitVector(object):
  882. "Fast bit-vector class based on Python built-in array type."
  883. def __init__(self, data=None, length=None):
  884. self.bits = array('B')
  885. self.bitlen = 0
  886. if data is not None:
  887. self.bits.extend(data)
  888. if length is None:
  889. self.bitlen = len(data) * 8
  890. else:
  891. self.bitlen = length
  892. def extend_to(self, length):
  893. "Extend vector to given bitlength."
  894. if length > self.bitlen:
  895. self.bits.extend([0] * ((length - self.bitlen + 7) // 8))
  896. self.bitlen = length
  897. def from_sixbit(self, data, pad=0):
  898. "Initialize bit vector from AIVDM-style six-bit armoring."
  899. self.bits.extend([0] * len(data))
  900. for ch in data:
  901. ch = ord(ch) - 48
  902. if ch > 40:
  903. ch -= 8
  904. for i in (5, 4, 3, 2, 1, 0):
  905. if (ch >> i) & 0x01:
  906. self.bits[self.bitlen // 8] |= (1 << (7 - self.bitlen % 8))
  907. self.bitlen += 1
  908. self.bitlen -= pad
  909. def ubits(self, start, width):
  910. "Extract a (zero-origin) bitfield from the buffer as an unsigned int."
  911. fld = 0
  912. for i in range(start // BITS_PER_BYTE,
  913. (start + width + BITS_PER_BYTE - 1) // BITS_PER_BYTE):
  914. fld <<= BITS_PER_BYTE
  915. fld |= self.bits[i]
  916. end = (start + width) % BITS_PER_BYTE
  917. if end != 0:
  918. fld >>= (BITS_PER_BYTE - end)
  919. fld &= ~(-1 << width)
  920. return fld
  921. def sbits(self, start, width):
  922. "Extract a (zero-origin) bitfield from the buffer as a signed int."
  923. fld = self.ubits(start, width)
  924. if fld & (1 << (width-1)):
  925. fld = -(2 ** width - fld)
  926. return fld
  927. def __len__(self):
  928. return self.bitlen
  929. def __repr__(self):
  930. "Used for dumping binary data."
  931. return (str(self.bitlen) + ":" +
  932. "".join(["%02x" %
  933. d for d in self.bits[:(self.bitlen + 7) // 8]]))
  934. class AISUnpackingException(exceptions.Exception):
  935. def __init__(self, lc, fieldname, value):
  936. self.lc = lc
  937. self.fieldname = fieldname
  938. self.value = value
  939. def __repr__(self):
  940. return ("%d: validation on fieldname %s failed (value %s)" %
  941. (self.lc, self.fieldname, self.value))
  942. def aivdm_unpack(lc, data, offset, values, instructions):
  943. "Unpack fields from data according to instructions."
  944. cooked = []
  945. for inst in instructions:
  946. if offset >= len(data):
  947. break
  948. elif (inst.conditional is not None and
  949. not inst.conditional(inst, values)):
  950. continue
  951. elif isinstance(inst, spare):
  952. offset += inst.width
  953. elif isinstance(inst, dispatch):
  954. i = inst.compute(values[inst.fieldname])
  955. # This is the recursion that lets us handle variant types
  956. cooked += aivdm_unpack(lc, data, offset, values, inst.subtypes[i])
  957. elif isinstance(inst, bitfield):
  958. if inst.type == 'unsigned':
  959. value = data.ubits(offset, inst.width)
  960. elif inst.type == 'signed':
  961. value = data.sbits(offset, inst.width)
  962. elif inst.type == 'string':
  963. value = ''
  964. # The try/catch error here is in case we run off the end
  965. # of a variable-length string field, as in messages 12 and 14
  966. try:
  967. for i in range(inst.width // 6):
  968. newchar = ("@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^- !\""
  969. "#$%&'()*+,-./0123456789:;<=>?"
  970. [data.ubits(offset + 6 * i, 6)])
  971. if newchar == '@':
  972. break
  973. else:
  974. value += newchar
  975. except IndexError:
  976. pass
  977. value = value.replace("@", " ").rstrip()
  978. elif inst.type == 'raw':
  979. # Note: Doesn't rely on the length.
  980. value = BitVector(data.bits[offset // 8:], len(data)-offset)
  981. values[inst.name] = value
  982. if inst.validator and not inst.validator(value):
  983. raise AISUnpackingException(lc, inst.name, value)
  984. offset += inst.width
  985. # An important thing about the unpacked representation this
  986. # generates is that it carries forward the meta-information from
  987. # the field type definition. This stuff is then available for
  988. # use by report-generating code.
  989. cooked.append([inst, value])
  990. return cooked
  991. def packet_scanner(source):
  992. "Get a span of AIVDM packets with contiguous fragment numbers."
  993. payloads = {'A': '', 'B': ''}
  994. raw = ''
  995. well_formed = False
  996. lc = 0
  997. while True:
  998. lc += 1
  999. line = source.readline()
  1000. if not line:
  1001. return
  1002. raw += line
  1003. line = line.strip()
  1004. # Strip off USCG metadata
  1005. line = re.sub(r"(?<=\*[0-9A-F][0-9A-F]),.*", "", line)
  1006. # Compute CRC-16 checksum
  1007. packet = line[1:-3] # Strip leading !, trailing * and CRC
  1008. csum = 0
  1009. for c in packet:
  1010. csum ^= ord(c)
  1011. csum = "%02X" % csum
  1012. # Ignore comments
  1013. if not line.startswith("!"):
  1014. continue
  1015. # Assemble fragments from single- and multi-line payloads
  1016. fields = line.split(",")
  1017. try:
  1018. expect = fields[1]
  1019. fragment = fields[2]
  1020. channel = fields[4]
  1021. if fragment == '1':
  1022. payloads[channel] = ''
  1023. well_formed = True
  1024. payloads[channel] += fields[5]
  1025. try:
  1026. # This works because a mangled pad literal means
  1027. # a malformed packet that will be caught by the CRC check.
  1028. pad = int(fields[6].split('*')[0])
  1029. except ValueError:
  1030. pad = 0
  1031. crc = fields[6].split('*')[1].strip()
  1032. except IndexError:
  1033. if skiperr:
  1034. sys.stderr.write("%d: malformed line: %s\n" %
  1035. (lc, line.strip()))
  1036. well_formed = False
  1037. else:
  1038. raise AISUnpackingException(lc, "checksum", crc)
  1039. if csum != crc:
  1040. if skiperr:
  1041. sys.stderr.write("%d: bad checksum %s, expecting %s: %s\n" %
  1042. (lc, repr(crc), csum, line.strip()))
  1043. well_formed = False
  1044. else:
  1045. raise AISUnpackingException(lc, "checksum", crc)
  1046. if fragment < expect or not well_formed:
  1047. continue
  1048. # Render assembled payload to packed bytes
  1049. bits = BitVector()
  1050. bits.from_sixbit(payloads[channel], pad)
  1051. yield (lc, raw, bits)
  1052. raw = ''
  1053. def postprocess(cooked):
  1054. "Postprocess cooked fields from a message."
  1055. # Handle type 21 name extension
  1056. if cooked[0][1] == 21 and len(cooked) > 19:
  1057. cooked[4][1] += cooked[19][1]
  1058. cooked.pop(-1)
  1059. return cooked
  1060. def parse_ais_messages(source, scaled=False, skiperr=False, verbose=0):
  1061. "Generator code - read forever from source stream, parsing AIS messages."
  1062. values = {}
  1063. for (lc, raw, bits) in packet_scanner(source):
  1064. values['length'] = bits.bitlen
  1065. # Without the following magic, we'd have a subtle problem near
  1066. # certain variable-length messages: DSV reports would
  1067. # sometimes have fewer fields than expected, because the
  1068. # unpacker would never generate cooked tuples for the omitted
  1069. # part of the message. Presently a known issue for types 15
  1070. # and 16 only. (Would never affect variable-length messages in
  1071. # which the last field type is 'string' or 'raw').
  1072. bits.extend_to(168)
  1073. # Magic recursive unpacking operation
  1074. try:
  1075. cooked = aivdm_unpack(lc, bits, 0, values, aivdm_decode)
  1076. # We now have a list of tuples containing unpacked fields
  1077. # Collect some field groups into ISO8601 format
  1078. for (offset, template, label, legend, formatter) in field_groups:
  1079. segment = cooked[offset:offset+len(template)]
  1080. if [x[0] for x in segment] == template:
  1081. group = formatter(*[x[1] for x in segment])
  1082. group = (label, group, 'string', legend, None)
  1083. cooked = (cooked[:offset] + [group] +
  1084. cooked[offset+len(template):])
  1085. # Apply the postprocessor stage
  1086. cooked = postprocess(cooked)
  1087. # Now apply custom formatting hooks.
  1088. if scaled:
  1089. for (i, (inst, value)) in enumerate(cooked):
  1090. if value == inst.oob:
  1091. cooked[i][1] = "n/a"
  1092. elif inst.formatter:
  1093. if isinstance(inst.formatter, tuple):
  1094. # Assumes 0 is the legend for the "undefined" value
  1095. if value >= len(inst.formatter):
  1096. value = 0
  1097. cooked[i][1] = inst.formatter[value]
  1098. elif isinstance(formatter, function):
  1099. cooked[i][1] = inst.formatter(value)
  1100. expected = lengths.get(values['msgtype'], None)
  1101. # Check length; has to be done after so we have the type field
  1102. bogon = False
  1103. if expected is not None:
  1104. if isinstance(expected, int):
  1105. expected_range = (expected, expected)
  1106. else:
  1107. expected_range = expected
  1108. actual = values['length']
  1109. if not (actual >= expected_range[0] and
  1110. actual <= expected_range[1]):
  1111. bogon = True
  1112. if skiperr:
  1113. sys.stderr.write(
  1114. "%d: type %d expected %s bits but saw %s: %s\n" %
  1115. (lc, values['msgtype'], expected,
  1116. actual, raw.strip().split()))
  1117. else:
  1118. raise AISUnpackingException(lc, "length", actual)
  1119. # We're done, hand back a decoding
  1120. values = {}
  1121. yield (raw, cooked, bogon)
  1122. raw = ''
  1123. except KeyboardInterrupt:
  1124. raise KeyboardInterrupt
  1125. except GeneratorExit:
  1126. raise GeneratorExit
  1127. except AISUnpackingException as e:
  1128. if skiperr:
  1129. sys.stderr.write("%s: %s\n" % (repr(e), raw.strip().split()))
  1130. continue
  1131. else:
  1132. raise
  1133. except Exception:
  1134. (exc_type, exc_value, exc_traceback) = sys.exc_info()
  1135. sys.stderr.write("%d: Unknown exception: %s\n" %
  1136. (lc, raw.strip().split()))
  1137. if skiperr:
  1138. continue
  1139. else:
  1140. reraise_with_traceback(exc_type, exc_value, exc_traceback)
  1141. # The rest is just sequencing and report generation.
  1142. if __name__ == "__main__":
  1143. import getopt
  1144. import sys
  1145. try:
  1146. (options, arguments) = getopt.getopt(sys.argv[1:], "cdhjmqst:vx")
  1147. except getopt.GetoptError as msg:
  1148. print("ais.py: " + str(msg))
  1149. raise SystemExit(1)
  1150. dsv = False
  1151. dump = False
  1152. histogram = False
  1153. json = False
  1154. malformed = False
  1155. quiet = False
  1156. scaled = False
  1157. types = []
  1158. frequencies = {}
  1159. verbose = 0
  1160. skiperr = True
  1161. for (switch, val) in options:
  1162. if switch == '-c': # Report in DSV format rather than JSON
  1163. dsv = True
  1164. elif switch == '-d': # Dump in a more human-readable format
  1165. dump = True
  1166. elif switch == '-h': # Make a histogram of type frequencies
  1167. histogram = True
  1168. elif switch == '-j': # Dump JSON
  1169. json = True
  1170. elif switch == '-m': # Dump malformed AIVDM/AIVDO packets raw
  1171. malformed = True
  1172. elif switch == '-q': # Suppress output
  1173. quiet = True
  1174. elif switch == '-s': # Report AIS in scaled form
  1175. scaled = True
  1176. elif switch == '-t': # Filter for a comma-separated list of types
  1177. types = list(map(int, val.split(",")))
  1178. elif switch == '-v': # Dump raw packet before JSON or DSV.
  1179. verbose += 1
  1180. elif switch == '-x': # Skip decoding errors
  1181. skiperr = False
  1182. if not dsv and not histogram and not json and not malformed and not quiet:
  1183. dump = True
  1184. try:
  1185. for ((raw, parsed, bogon) in
  1186. parse_ais_messages(sys.stdin, scaled, skiperr, verbose)):
  1187. msgtype = parsed[0][1]
  1188. if types and msgtype not in types:
  1189. continue
  1190. if verbose >= 1 or (bogon and malformed):
  1191. sys.stdout.write(raw)
  1192. if not bogon:
  1193. if json:
  1194. def quotify(x):
  1195. if isinstance(x, str):
  1196. return '"' + str(x) + '"'
  1197. else:
  1198. return str(x)
  1199. print("{" + ",".join(['"' + x[0].name + '":' +
  1200. quotify(x[1]) for x in parsed]) + "}")
  1201. elif dsv:
  1202. print("|".join([str(x[1]) for x in parsed]))
  1203. elif histogram:
  1204. key = "%02d" % msgtype
  1205. frequencies[key] = frequencies.get(key, 0) + 1
  1206. if msgtype == 6 or msgtype == 8:
  1207. dac = 0
  1208. fid = 0
  1209. if msgtype == 8:
  1210. dac = parsed[3][1]
  1211. fid = parsed[4][1]
  1212. elif msgtype == 6:
  1213. dac = parsed[6][1]
  1214. fid = parsed[7][1]
  1215. key = "%02d_%04d_%02d" % (msgtype, dac, fid)
  1216. frequencies[key] = frequencies.get(key, 0) + 1
  1217. elif dump:
  1218. for (inst, value) in parsed:
  1219. print("%-25s: %s" % (inst.legend, value))
  1220. print("%%")
  1221. sys.stdout.flush()
  1222. if histogram:
  1223. keys = list(frequencies.keys())
  1224. keys.sort()
  1225. for msgtype in keys:
  1226. print("%-33s\t%d" % (msgtype, frequencies[msgtype]))
  1227. except KeyboardInterrupt:
  1228. pass
  1229. # End