kopia lustrzana https://github.com/cyoung/stratux
				
				
				
			
		
			
				
	
	
		
			668 wiersze
		
	
	
		
			21 KiB
		
	
	
	
		
			Go
		
	
	
		
			Executable File
		
	
			
		
		
	
	
			668 wiersze
		
	
	
		
			21 KiB
		
	
	
	
		
			Go
		
	
	
		
			Executable File
		
	
package uatparse
 | 
						|
 | 
						|
import (
 | 
						|
	"encoding/hex"
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"io/ioutil"
 | 
						|
	"strconv"
 | 
						|
	"strings"
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	UPLINK_BLOCK_DATA_BITS  = 576
 | 
						|
	UPLINK_BLOCK_BITS       = (UPLINK_BLOCK_DATA_BITS + 160)
 | 
						|
	UPLINK_BLOCK_DATA_BYTES = (UPLINK_BLOCK_DATA_BITS / 8)
 | 
						|
	UPLINK_BLOCK_BYTES      = (UPLINK_BLOCK_BITS / 8)
 | 
						|
 | 
						|
	UPLINK_FRAME_BLOCKS     = 6
 | 
						|
	UPLINK_FRAME_DATA_BITS  = (UPLINK_FRAME_BLOCKS * UPLINK_BLOCK_DATA_BITS)
 | 
						|
	UPLINK_FRAME_BITS       = (UPLINK_FRAME_BLOCKS * UPLINK_BLOCK_BITS)
 | 
						|
	UPLINK_FRAME_DATA_BYTES = (UPLINK_FRAME_DATA_BITS / 8)
 | 
						|
	UPLINK_FRAME_BYTES      = (UPLINK_FRAME_BITS / 8)
 | 
						|
 | 
						|
	// assume 6 byte frames: 2 header bytes, 4 byte payload
 | 
						|
	// (TIS-B heartbeat with one address, or empty FIS-B APDU)
 | 
						|
	UPLINK_MAX_INFO_FRAMES = (424 / 6)
 | 
						|
 | 
						|
	dlac_alpha = "\x03ABCDEFGHIJKLMNOPQRSTUVWXYZ\x1A\t\x1E\n| !\"#$%&'()*+,-./0123456789:;<=>?"
 | 
						|
)
 | 
						|
 | 
						|
type UATFrame struct {
 | 
						|
	Raw_data     []byte
 | 
						|
	FISB_data    []byte
 | 
						|
	FISB_month   uint32
 | 
						|
	FISB_day     uint32
 | 
						|
	FISB_hours   uint32
 | 
						|
	FISB_minutes uint32
 | 
						|
	FISB_seconds uint32
 | 
						|
 | 
						|
	FISB_length uint32
 | 
						|
 | 
						|
	frame_length uint32
 | 
						|
	Frame_type   uint32
 | 
						|
 | 
						|
	Product_id uint32
 | 
						|
	// Text data, if applicable.
 | 
						|
	Text_data []string
 | 
						|
 | 
						|
	// Flags.
 | 
						|
	a_f bool
 | 
						|
	g_f bool
 | 
						|
	p_f bool
 | 
						|
	s_f bool //TODO: Segmentation.
 | 
						|
 | 
						|
	// For AIRMET/NOTAM.
 | 
						|
	//FIXME: Temporary.
 | 
						|
	Points             []GeoPoint
 | 
						|
	ReportNumber       uint16
 | 
						|
	ReportYear         uint16
 | 
						|
	LocationIdentifier string
 | 
						|
	RecordFormat       uint8
 | 
						|
	ReportStart        string
 | 
						|
	ReportEnd          string
 | 
						|
 | 
						|
	// For NEXRAD.
 | 
						|
	NEXRAD []NEXRADBlock
 | 
						|
}
 | 
						|
 | 
						|
type UATMsg struct {
 | 
						|
	// Metadata from demodulation.
 | 
						|
	RS_Err         int
 | 
						|
	SignalStrength int
 | 
						|
	msg            []byte
 | 
						|
	decoded        bool
 | 
						|
	// Station location for uplink frames, aircraft position for downlink frames.
 | 
						|
	Lat    float64
 | 
						|
	Lon    float64
 | 
						|
	Frames []*UATFrame
 | 
						|
}
 | 
						|
 | 
						|
func dlac_decode(data []byte, data_len uint32) string {
 | 
						|
	step := 0
 | 
						|
	tab := false
 | 
						|
	ret := ""
 | 
						|
	for i := uint32(0); i < data_len; i++ {
 | 
						|
		var ch uint32
 | 
						|
		switch step {
 | 
						|
		case 0:
 | 
						|
			ch = uint32(data[i+0]) >> 2
 | 
						|
		case 1:
 | 
						|
			ch = ((uint32(data[i-1]) & 0x03) << 4) | (uint32(data[i+0]) >> 4)
 | 
						|
		case 2:
 | 
						|
			ch = ((uint32(data[i-1]) & 0x0f) << 2) | (uint32(data[i+0]) >> 6)
 | 
						|
			i = i - 1
 | 
						|
		case 3:
 | 
						|
			ch = uint32(data[i+0]) & 0x3f
 | 
						|
		}
 | 
						|
		if tab {
 | 
						|
			for ch > 0 {
 | 
						|
				ret += " "
 | 
						|
				ch--
 | 
						|
			}
 | 
						|
			tab = false
 | 
						|
		} else if ch == 28 { // tab
 | 
						|
			tab = true
 | 
						|
		} else {
 | 
						|
			ret += string(dlac_alpha[ch])
 | 
						|
		}
 | 
						|
		step = (step + 1) % 4
 | 
						|
	}
 | 
						|
	return ret
 | 
						|
}
 | 
						|
 | 
						|
// Decodes the time format and aligns 'FISB_data' accordingly.
 | 
						|
//TODO: Make a new "FISB Time" structure that also encodes the type of timestamp received.
 | 
						|
//TODO: pass up error.
 | 
						|
func (f *UATFrame) decodeTimeFormat() {
 | 
						|
	if len(f.Raw_data) < 3 {
 | 
						|
		return // Can't determine time format.
 | 
						|
	}
 | 
						|
 | 
						|
	t_opt := ((uint32(f.Raw_data[1]) & 0x01) << 1) | (uint32(f.Raw_data[2]) >> 7)
 | 
						|
 | 
						|
	var fisb_data []byte
 | 
						|
	switch t_opt {
 | 
						|
	case 0: // Hours, Minutes.
 | 
						|
		if f.frame_length < 4 {
 | 
						|
			return
 | 
						|
		}
 | 
						|
		f.FISB_hours = (uint32(f.Raw_data[2]) & 0x7c) >> 2
 | 
						|
		f.FISB_minutes = ((uint32(f.Raw_data[2]) & 0x03) << 4) | (uint32(f.Raw_data[3]) >> 4)
 | 
						|
		f.FISB_length = f.frame_length - 4
 | 
						|
		fisb_data = f.Raw_data[4:]
 | 
						|
	case 1: // Hours, Minutes, Seconds.
 | 
						|
		if f.frame_length < 5 {
 | 
						|
			return
 | 
						|
		}
 | 
						|
		f.FISB_hours = (uint32(f.Raw_data[2]) & 0x7c) >> 2
 | 
						|
		f.FISB_minutes = ((uint32(f.Raw_data[2]) & 0x03) << 4) | (uint32(f.Raw_data[3]) >> 4)
 | 
						|
		f.FISB_seconds = ((uint32(f.Raw_data[3]) & 0x0f) << 2) | (uint32(f.Raw_data[4]) >> 6)
 | 
						|
		f.FISB_length = f.frame_length - 5
 | 
						|
		fisb_data = f.Raw_data[5:]
 | 
						|
	case 2: // Month, Day, Hours, Minutes.
 | 
						|
		if f.frame_length < 5 {
 | 
						|
			return
 | 
						|
		}
 | 
						|
		f.FISB_month = (uint32(f.Raw_data[2]) & 0x78) >> 3
 | 
						|
		f.FISB_day = ((uint32(f.Raw_data[2]) & 0x07) << 2) | (uint32(f.Raw_data[3]) >> 6)
 | 
						|
		f.FISB_hours = (uint32(f.Raw_data[3]) & 0x3e) >> 1
 | 
						|
		f.FISB_minutes = ((uint32(f.Raw_data[3]) & 0x01) << 5) | (uint32(f.Raw_data[4]) >> 3)
 | 
						|
		f.FISB_length = f.frame_length - 5
 | 
						|
		fisb_data = f.Raw_data[5:]
 | 
						|
	case 3: // Month, Day, Hours, Minutes, Seconds.
 | 
						|
		if f.frame_length < 6 {
 | 
						|
			return
 | 
						|
		}
 | 
						|
		f.FISB_month = (uint32(f.Raw_data[2]) & 0x78) >> 3
 | 
						|
		f.FISB_day = ((uint32(f.Raw_data[2]) & 0x07) << 2) | (uint32(f.Raw_data[3]) >> 6)
 | 
						|
		f.FISB_hours = (uint32(f.Raw_data[3]) & 0x3e) >> 1
 | 
						|
		f.FISB_minutes = ((uint32(f.Raw_data[3]) & 0x01) << 5) | (uint32(f.Raw_data[4]) >> 3)
 | 
						|
		f.FISB_seconds = ((uint32(f.Raw_data[4]) & 0x03) << 3) | (uint32(f.Raw_data[5]) >> 5)
 | 
						|
		f.FISB_length = f.frame_length - 6
 | 
						|
		fisb_data = f.Raw_data[6:]
 | 
						|
	default:
 | 
						|
		return // Should never reach this.
 | 
						|
	}
 | 
						|
 | 
						|
	f.FISB_data = fisb_data
 | 
						|
 | 
						|
	if (uint16(f.Raw_data[1]) & 0x02) != 0 {
 | 
						|
		f.s_f = true // Default false.
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Format newlines.
 | 
						|
func formatDLACData(p string) []string {
 | 
						|
	ret := make([]string, 0)
 | 
						|
	for {
 | 
						|
		pos := strings.Index(p, "\x1E")
 | 
						|
		if pos == -1 {
 | 
						|
			pos = strings.Index(p, "\x03")
 | 
						|
			if pos == -1 {
 | 
						|
				ret = append(ret, p)
 | 
						|
				break
 | 
						|
			}
 | 
						|
		}
 | 
						|
		ret = append(ret, p[:pos])
 | 
						|
		p = p[pos+1:]
 | 
						|
	}
 | 
						|
	return ret
 | 
						|
}
 | 
						|
 | 
						|
// Whole frame contents is DLAC encoded text.
 | 
						|
func (f *UATFrame) decodeTextFrame() {
 | 
						|
	if len(f.FISB_data) < int(f.FISB_length) {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	p := dlac_decode(f.FISB_data, f.FISB_length)
 | 
						|
 | 
						|
	f.Text_data = formatDLACData(p)
 | 
						|
}
 | 
						|
 | 
						|
// Gets month, day, hours, minutes.
 | 
						|
// Formats into a string.
 | 
						|
func airmetParseDate(b []byte, date_time_format uint8) string {
 | 
						|
	switch date_time_format {
 | 
						|
	case 0: // No date/time used.
 | 
						|
		return ""
 | 
						|
	case 1: // Month, Day, Hours, Minutes.
 | 
						|
		month := uint8(b[0])
 | 
						|
		day := uint8(b[1])
 | 
						|
		hours := uint8(b[2])
 | 
						|
		minutes := uint8(b[3])
 | 
						|
		return fmt.Sprintf("%02d-%02d %02d:%02d", month, day, hours, minutes)
 | 
						|
	case 2: // Day, Hours, Minutes.
 | 
						|
		day := uint8(b[0])
 | 
						|
		hours := uint8(b[1])
 | 
						|
		minutes := uint8(b[2])
 | 
						|
		return fmt.Sprintf("%02d %02d:%02d", day, hours, minutes)
 | 
						|
	case 3: // Hours, Minutes.
 | 
						|
		hours := uint8(b[0])
 | 
						|
		minutes := uint8(b[1])
 | 
						|
		return fmt.Sprintf("%02d:%02d", hours, minutes)
 | 
						|
	}
 | 
						|
 | 
						|
	return ""
 | 
						|
}
 | 
						|
 | 
						|
func airmetLatLng(lat_raw, lng_raw int32, alt bool) (float64, float64) {
 | 
						|
	fct := float64(0.000687)
 | 
						|
	if alt {
 | 
						|
		fct = float64(0.001373)
 | 
						|
	}
 | 
						|
	lat := fct * float64(lat_raw)
 | 
						|
	lng := fct * float64(lng_raw)
 | 
						|
	if lat > 90.0 {
 | 
						|
		lat = lat - 180.0
 | 
						|
	}
 | 
						|
	if lng > 180.0 {
 | 
						|
		lng = lng - 360.0
 | 
						|
	}
 | 
						|
	return lat, lng
 | 
						|
}
 | 
						|
 | 
						|
//TODO: Ignoring flags (segmentation, etc.)
 | 
						|
// Aero_FISB_ProdDef_Rev4.pdf
 | 
						|
// Decode product IDs 8-13.
 | 
						|
func (f *UATFrame) decodeAirmet() {
 | 
						|
	// APDU header: 48 bits  (3-3) - assume no segmentation.
 | 
						|
 | 
						|
	record_format := (uint8(f.FISB_data[0]) & 0xF0) >> 4
 | 
						|
	f.RecordFormat = record_format
 | 
						|
	fmt.Fprintf(ioutil.Discard, "record_format=%d\n", record_format)
 | 
						|
	product_version := (uint8(f.FISB_data[0]) & 0x0F)
 | 
						|
	fmt.Fprintf(ioutil.Discard, "product_version=%d\n", product_version)
 | 
						|
	record_count := (uint8(f.FISB_data[1]) & 0xF0) >> 4
 | 
						|
	fmt.Fprintf(ioutil.Discard, "record_count=%d\n", record_count)
 | 
						|
	location_identifier := dlac_decode(f.FISB_data[2:], 3)
 | 
						|
	fmt.Fprintf(ioutil.Discard, "%s\n", hex.Dump(f.FISB_data))
 | 
						|
	f.LocationIdentifier = location_identifier
 | 
						|
	fmt.Fprintf(ioutil.Discard, "location_identifier=%s\n", location_identifier)
 | 
						|
	record_reference := (uint8(f.FISB_data[5])) //FIXME: Special values. 0x00 means "use location_identifier". 0xFF means "use different reference". (4-3).
 | 
						|
	fmt.Fprintf(ioutil.Discard, "record_reference=%d\n", record_reference)
 | 
						|
	// Not sure when this is even used.
 | 
						|
	// rwy_designator := (record_reference & FC) >> 4
 | 
						|
	// parallel_rwy_designator := record_reference & 0x03 // 0 = NA, 1 = R, 2 = L, 3 = C (Figure 4-2).
 | 
						|
 | 
						|
	//FIXME: Assume one record.
 | 
						|
	if record_count != 1 {
 | 
						|
		fmt.Fprintf(ioutil.Discard, "record_count=%d, != 1\n", record_count)
 | 
						|
		return
 | 
						|
	}
 | 
						|
	/*
 | 
						|
		0 - No data
 | 
						|
		1 - Unformatted ASCII Text
 | 
						|
		2 - Unformatted DLAC Text
 | 
						|
		3 - Unformatted DLAC Text w/ dictionary
 | 
						|
		4 - Formatted Text using ASN.1/PER
 | 
						|
		5-7 - Future Use
 | 
						|
		8 - Graphical Overlay
 | 
						|
		9-15 - Future Use
 | 
						|
	*/
 | 
						|
	switch record_format {
 | 
						|
	case 2:
 | 
						|
		record_length := (uint16(f.FISB_data[6]) << 8) | uint16(f.FISB_data[7])
 | 
						|
		if len(f.FISB_data)-int(record_length) < 6 {
 | 
						|
			fmt.Fprintf(ioutil.Discard, "FISB record not long enough: record_length=%d, len(f.FISB_data)=%d\n", record_length, len(f.FISB_data))
 | 
						|
			return
 | 
						|
		}
 | 
						|
		fmt.Fprintf(ioutil.Discard, "record_length=%d\n", record_length)
 | 
						|
		// Report identifier = report number + report year.
 | 
						|
		report_number := (uint16(f.FISB_data[8]) << 6) | ((uint16(f.FISB_data[9]) & 0xFC) >> 2)
 | 
						|
		f.ReportNumber = report_number
 | 
						|
		fmt.Fprintf(ioutil.Discard, "report_number=%d\n", report_number)
 | 
						|
		report_year := ((uint16(f.FISB_data[9]) & 0x03) << 5) | ((uint16(f.FISB_data[10]) & 0xF8) >> 3)
 | 
						|
		f.ReportYear = report_year
 | 
						|
		fmt.Fprintf(ioutil.Discard, "report_year=%d\n", report_year)
 | 
						|
		report_status := (uint8(f.FISB_data[10]) & 0x04) >> 2 //TODO: 0 = cancelled, 1 = active.
 | 
						|
		fmt.Fprintf(ioutil.Discard, "report_status=%d\n", report_status)
 | 
						|
		fmt.Fprintf(ioutil.Discard, "record_length=%d,len=%d\n", record_length, len(f.FISB_data))
 | 
						|
		text_data_len := record_length - 5
 | 
						|
		text_data := dlac_decode(f.FISB_data[11:], uint32(text_data_len))
 | 
						|
		fmt.Fprintf(ioutil.Discard, "text_data=%s\n", text_data)
 | 
						|
		f.Text_data = formatDLACData(text_data)
 | 
						|
	case 8:
 | 
						|
		// (6-1). (6.22 - Graphical Overlay Record Format).
 | 
						|
		record_data := f.FISB_data[6:] // Start after the record header.
 | 
						|
		record_length := (uint16(record_data[0]) << 2) | ((uint16(record_data[1]) & 0xC0) >> 6)
 | 
						|
		fmt.Fprintf(ioutil.Discard, "record_length=%d\n", record_length)
 | 
						|
		// Report identifier = report number + report year.
 | 
						|
		report_number := ((uint16(record_data[1]) & 0x3F) << 8) | uint16(record_data[2])
 | 
						|
		f.ReportNumber = report_number
 | 
						|
		fmt.Fprintf(ioutil.Discard, "report_number=%d\n", report_number)
 | 
						|
		report_year := (uint16(record_data[3]) & 0xFE) >> 1
 | 
						|
		f.ReportYear = report_year
 | 
						|
		fmt.Fprintf(ioutil.Discard, "report_year=%d\n", report_year)
 | 
						|
		overlay_record_identifier := ((uint8(record_data[4]) & 0x1E) >> 1) + 1 // Document instructs to add 1.
 | 
						|
		fmt.Fprintf(ioutil.Discard, "overlay_record_identifier=%d\n", overlay_record_identifier)
 | 
						|
		object_label_flag := uint8(record_data[4] & 0x01)
 | 
						|
		fmt.Fprintf(ioutil.Discard, "object_label_flag=%d\n", object_label_flag)
 | 
						|
 | 
						|
		if object_label_flag == 0 { // Numeric index.
 | 
						|
			object_label := (uint8(record_data[5]) << 8) | uint8(record_data[6])
 | 
						|
			record_data = record_data[7:]
 | 
						|
			fmt.Fprintf(ioutil.Discard, "object_label=%d\n", object_label)
 | 
						|
		} else {
 | 
						|
			object_label := dlac_decode(record_data[5:], 9)
 | 
						|
			record_data = record_data[14:]
 | 
						|
			fmt.Fprintf(ioutil.Discard, "object_label=%s\n", object_label)
 | 
						|
		}
 | 
						|
 | 
						|
		element_flag := (uint8(record_data[0]) & 0x80) >> 7
 | 
						|
		fmt.Fprintf(ioutil.Discard, "element_flag=%d\n", element_flag)
 | 
						|
		qualifier_flag := (uint8(record_data[0]) & 0x40) >> 6
 | 
						|
		fmt.Fprintf(ioutil.Discard, "qualifier_flag=%d\n", qualifier_flag)
 | 
						|
		param_flag := (uint8(record_data[0]) & 0x20) >> 5
 | 
						|
		fmt.Fprintf(ioutil.Discard, "param_flag=%d\n", param_flag)
 | 
						|
		object_element := uint8(record_data[0]) & 0x1F
 | 
						|
		fmt.Fprintf(ioutil.Discard, "object_element=%d\n", object_element)
 | 
						|
 | 
						|
		object_type := (uint8(record_data[1]) & 0xF0) >> 4
 | 
						|
		fmt.Fprintf(ioutil.Discard, "object_type=%d\n", object_type)
 | 
						|
 | 
						|
		object_status := uint8(record_data[1]) & 0x0F
 | 
						|
		fmt.Fprintf(ioutil.Discard, "object_status=%d\n", object_status)
 | 
						|
 | 
						|
		//FIXME
 | 
						|
		if qualifier_flag == 0 { //TODO: Check.
 | 
						|
			record_data = record_data[2:]
 | 
						|
		} else {
 | 
						|
			object_qualifier := (uint32(record_data[2]) << 16) | (uint32(record_data[3]) << 8) | uint32(record_data[4])
 | 
						|
			fmt.Fprintf(ioutil.Discard, "object_qualifier=%d\n", object_qualifier)
 | 
						|
			fmt.Fprintf(ioutil.Discard, "%02x%02x%02x\n", record_data[2], record_data[3], record_data[4])
 | 
						|
			record_data = record_data[5:]
 | 
						|
		}
 | 
						|
		//FIXME
 | 
						|
		//if param_flag == 0 { //TODO: Check.
 | 
						|
		//	record_data = record_data[2:]
 | 
						|
		//} else {
 | 
						|
		//	//TODO.
 | 
						|
		//	//			record_data = record_data[4:]
 | 
						|
		//}
 | 
						|
 | 
						|
		record_applicability_options := (uint8(record_data[0]) & 0xC0) >> 6
 | 
						|
		fmt.Fprintf(ioutil.Discard, "record_applicability_options=%d\n", record_applicability_options)
 | 
						|
		date_time_format := (uint8(record_data[0]) & 0x30) >> 4
 | 
						|
		fmt.Fprintf(ioutil.Discard, "date_time_format=%d\n", date_time_format)
 | 
						|
		geometry_overlay_options := uint8(record_data[0]) & 0x0F
 | 
						|
		fmt.Fprintf(ioutil.Discard, "geometry_overlay_options=%d\n", geometry_overlay_options)
 | 
						|
 | 
						|
		overlay_operator := (uint8(record_data[1]) & 0xC0) >> 6
 | 
						|
		fmt.Fprintf(ioutil.Discard, "overlay_operator=%d\n", overlay_operator)
 | 
						|
 | 
						|
		overlay_vertices_count := (uint8(record_data[1]) & 0x3F) + 1 // Document instructs to add 1. (6.20).
 | 
						|
		fmt.Fprintf(ioutil.Discard, "overlay_vertices_count=%d\n", overlay_vertices_count)
 | 
						|
 | 
						|
		// Parse all of the dates.
 | 
						|
		switch record_applicability_options {
 | 
						|
		case 0: // No times given. UFN.
 | 
						|
			record_data = record_data[2:]
 | 
						|
		case 1: // Start time only. WEF.
 | 
						|
			f.ReportStart = airmetParseDate(record_data[2:], date_time_format)
 | 
						|
			record_data = record_data[6:]
 | 
						|
		case 2: // End time only. TIL.
 | 
						|
			f.ReportEnd = airmetParseDate(record_data[2:], date_time_format)
 | 
						|
			record_data = record_data[6:]
 | 
						|
		case 3: // Both start and end times. WEF.
 | 
						|
			f.ReportStart = airmetParseDate(record_data[2:], date_time_format)
 | 
						|
			f.ReportEnd = airmetParseDate(record_data[6:], date_time_format)
 | 
						|
			record_data = record_data[10:]
 | 
						|
		}
 | 
						|
 | 
						|
		// Now we have the vertices.
 | 
						|
		switch geometry_overlay_options {
 | 
						|
		case 3: // Extended Range 3D Polygon (MSL).
 | 
						|
			points := make([]GeoPoint, 0) // Slice containing all of the points.
 | 
						|
			fmt.Fprintf(ioutil.Discard, "%d\n", len(record_data))
 | 
						|
			for i := 0; i < int(overlay_vertices_count); i++ {
 | 
						|
				lng_raw := (int32(record_data[6*i]) << 11) | (int32(record_data[6*i+1]) << 3) | (int32(record_data[6*i+2]) & 0xE0 >> 5)
 | 
						|
				lat_raw := ((int32(record_data[6*i+2]) & 0x1F) << 14) | (int32(record_data[6*i+3]) << 6) | ((int32(record_data[6*i+4]) & 0xFC) >> 2)
 | 
						|
				alt_raw := ((int32(record_data[6*i+4]) & 0x03) << 8) | int32(record_data[6*i+5])
 | 
						|
 | 
						|
				fmt.Fprintf(ioutil.Discard, "lat_raw=%d, lng_raw=%d, alt_raw=%d\n", lat_raw, lng_raw, alt_raw)
 | 
						|
				lat, lng := airmetLatLng(lat_raw, lng_raw, false)
 | 
						|
 | 
						|
				alt := alt_raw * 100
 | 
						|
				fmt.Fprintf(ioutil.Discard, "lat=%f,lng=%f,alt=%d\n", lat, lng, alt)
 | 
						|
				fmt.Fprintf(ioutil.Discard, "coord:%f,%f\n", lat, lng)
 | 
						|
				var point GeoPoint
 | 
						|
				point.Lat = lat
 | 
						|
				point.Lon = lng
 | 
						|
				point.Alt = alt
 | 
						|
				points = append(points, point)
 | 
						|
				f.Points = points
 | 
						|
			}
 | 
						|
		case 9: // Extended Range 3D Point (AGL). p.47.
 | 
						|
			if len(record_data) < 6 {
 | 
						|
				fmt.Fprintf(ioutil.Discard, "invalid data: Extended Range 3D Point. Should be 6 bytes; % seen.\n", len(record_data))
 | 
						|
			} else {
 | 
						|
				lng_raw := (int32(record_data[0]) << 11) | (int32(record_data[1]) << 3) | (int32(record_data[2]) & 0xE0 >> 5)
 | 
						|
				lat_raw := ((int32(record_data[2]) & 0x1F) << 14) | (int32(record_data[3]) << 6) | ((int32(record_data[4]) & 0xFC) >> 2)
 | 
						|
				alt_raw := ((int32(record_data[4]) & 0x03) << 8) | int32(record_data[5])
 | 
						|
 | 
						|
				fmt.Fprintf(ioutil.Discard, "lat_raw=%d, lng_raw=%d, alt_raw=%d\n", lat_raw, lng_raw, alt_raw)
 | 
						|
				lat, lng := airmetLatLng(lat_raw, lng_raw, false)
 | 
						|
 | 
						|
				alt := alt_raw * 100
 | 
						|
				fmt.Fprintf(ioutil.Discard, "lat=%f,lng=%f,alt=%d\n", lat, lng, alt)
 | 
						|
				fmt.Fprintf(ioutil.Discard, "coord:%f,%f\n", lat, lng)
 | 
						|
				var point GeoPoint
 | 
						|
				point.Lat = lat
 | 
						|
				point.Lon = lng
 | 
						|
				point.Alt = alt
 | 
						|
				f.Points = []GeoPoint{point}
 | 
						|
			}
 | 
						|
		case 7, 8: // Extended Range Circular Prism (7 = MSL, 8 = AGL)
 | 
						|
			if len(record_data) < 14 {
 | 
						|
				fmt.Fprintf(ioutil.Discard, "invalid data: Extended Range Circular Prism. Should be 14 bytes; % seen.\n", len(record_data))
 | 
						|
			} else {
 | 
						|
 | 
						|
				lng_bot_raw := (int32(record_data[0]) << 10) | (int32(record_data[1]) << 2) | (int32(record_data[2]) & 0xC0 >> 6)
 | 
						|
				lat_bot_raw := ((int32(record_data[2]) & 0x3F) << 12) | (int32(record_data[3]) << 4) | ((int32(record_data[4]) & 0xF0) >> 4)
 | 
						|
				lng_top_raw := ((int32(record_data[4]) & 0x0F) << 14) | (int32(record_data[5]) << 6) | ((int32(record_data[6]) & 0xFC) >> 2)
 | 
						|
				lat_top_raw := ((int32(record_data[6]) & 0x03) << 16) | (int32(record_data[7]) << 8) | int32(record_data[8])
 | 
						|
 | 
						|
				alt_bot_raw := (int32(record_data[9]) & 0xFE) >> 1
 | 
						|
				alt_top_raw := ((int32(record_data[9]) & 0x01) << 6) | ((int32(record_data[10]) & 0xFC) >> 2)
 | 
						|
 | 
						|
				r_lng_raw := ((int32(record_data[10]) & 0x03) << 7) | ((int32(record_data[11]) & 0xFE) >> 1)
 | 
						|
				r_lat_raw := ((int32(record_data[11]) & 0x01) << 8) | int32(record_data[12])
 | 
						|
				alpha := int32(record_data[13])
 | 
						|
 | 
						|
				lat_bot, lng_bot := airmetLatLng(lat_bot_raw, lng_bot_raw, true)
 | 
						|
				lat_top, lng_top := airmetLatLng(lat_top_raw, lng_top_raw, true)
 | 
						|
 | 
						|
				alt_bot := alt_bot_raw * 5
 | 
						|
				alt_top := alt_top_raw * 500
 | 
						|
 | 
						|
				r_lng := float64(r_lng_raw) * float64(0.2)
 | 
						|
				r_lat := float64(r_lat_raw) * float64(0.2)
 | 
						|
 | 
						|
				fmt.Fprintf(ioutil.Discard, "lat_bot, lng_bot = %f, %f\n", lat_bot, lng_bot)
 | 
						|
				fmt.Fprintf(ioutil.Discard, "lat_top, lng_top = %f, %f\n", lat_top, lng_top)
 | 
						|
 | 
						|
				if geometry_overlay_options == 8 {
 | 
						|
					fmt.Fprintf(ioutil.Discard, "alt_bot, alt_top = %d AGL, %d AGL\n", alt_bot, alt_top)
 | 
						|
				} else {
 | 
						|
					fmt.Fprintf(ioutil.Discard, "alt_bot, alt_top = %d MSL, %d MSL\n", alt_bot, alt_top)
 | 
						|
				}
 | 
						|
				fmt.Fprintf(ioutil.Discard, "r_lng, r_lat = %f, %f\n", r_lng, r_lat)
 | 
						|
 | 
						|
				fmt.Fprintf(ioutil.Discard, "alpha=%d\n", alpha)
 | 
						|
			}
 | 
						|
		default:
 | 
						|
			fmt.Fprintf(ioutil.Discard, "unknown geometry: %d\n", geometry_overlay_options)
 | 
						|
		}
 | 
						|
	//case 1: // Unformatted ASCII Text.
 | 
						|
	default:
 | 
						|
		fmt.Fprintf(ioutil.Discard, "unknown record format: %d\n", record_format)
 | 
						|
	}
 | 
						|
	fmt.Fprintf(ioutil.Discard, "\n\n\n")
 | 
						|
}
 | 
						|
 | 
						|
func (f *UATFrame) decodeInfoFrame() {
 | 
						|
 | 
						|
	if len(f.Raw_data) < 2 {
 | 
						|
		return // Can't determine Product_id.
 | 
						|
	}
 | 
						|
 | 
						|
	f.Product_id = ((uint32(f.Raw_data[0]) & 0x1f) << 6) | (uint32(f.Raw_data[1]) >> 2)
 | 
						|
 | 
						|
	if f.Frame_type != 0 {
 | 
						|
		return // Not FIS-B.
 | 
						|
	}
 | 
						|
 | 
						|
	f.decodeTimeFormat()
 | 
						|
 | 
						|
	switch f.Product_id {
 | 
						|
	case 413:
 | 
						|
		f.decodeTextFrame()
 | 
						|
		/*
 | 
						|
			case 8, 11, 13:
 | 
						|
				f.decodeAirmet()
 | 
						|
		*/
 | 
						|
	case 63, 64:
 | 
						|
		f.decodeNexradFrame()
 | 
						|
 | 
						|
	default:
 | 
						|
		fmt.Fprintf(ioutil.Discard, "don't know what to do with product id: %d\n", f.Product_id)
 | 
						|
	}
 | 
						|
 | 
						|
	//	logger.Printf("pos=%d,len=%d,t_opt=%d,product_id=%d, time=%d:%d\n", frame_start, frame_len, t_opt, product_id, fisb_hours, fisb_minutes)
 | 
						|
}
 | 
						|
 | 
						|
func (u *UATMsg) DecodeUplink() error {
 | 
						|
	//	position_valid := (uint32(frame[5]) & 0x01) != 0
 | 
						|
	frame := u.msg
 | 
						|
 | 
						|
	if len(frame) < UPLINK_FRAME_DATA_BYTES {
 | 
						|
		return errors.New(fmt.Sprintf("DecodeUplink: short read (%d).", len(frame)))
 | 
						|
	}
 | 
						|
 | 
						|
	raw_lat := (uint32(frame[0]) << 15) | (uint32(frame[1]) << 7) | (uint32(frame[2]) >> 1)
 | 
						|
 | 
						|
	raw_lon := ((uint32(frame[2]) & 0x01) << 23) | (uint32(frame[3]) << 15) | (uint32(frame[4]) << 7) | (uint32(frame[5]) >> 1)
 | 
						|
	lat := float64(raw_lat) * 360.0 / 16777216.0
 | 
						|
	lon := float64(raw_lon) * 360.0 / 16777216.0
 | 
						|
 | 
						|
	if lat > 90 {
 | 
						|
		lat = lat - 180
 | 
						|
	}
 | 
						|
	if lon > 180 {
 | 
						|
		lon = lon - 360
 | 
						|
	}
 | 
						|
 | 
						|
	u.Lat = lat
 | 
						|
	u.Lon = lon
 | 
						|
 | 
						|
	//	utc_coupled := (uint32(frame[6]) & 0x80) != 0
 | 
						|
	app_data_valid := (uint32(frame[6]) & 0x20) != 0
 | 
						|
	//	slot_id := uint32(frame[6]) & 0x1f
 | 
						|
	//	tisb_site_id := uint32(frame[7]) >> 4
 | 
						|
 | 
						|
	//	logger.Printf("position_valid=%t, %.04f, %.04f, %t, %t, %d, %d\n", position_valid, lat, lon, utc_coupled, app_data_valid, slot_id, tisb_site_id)
 | 
						|
 | 
						|
	if !app_data_valid {
 | 
						|
		return nil // Not sure when this even happens?
 | 
						|
	}
 | 
						|
 | 
						|
	app_data := frame[8:432]
 | 
						|
	num_info_frames := 0
 | 
						|
	pos := 0
 | 
						|
	total_len := len(app_data)
 | 
						|
	for (num_info_frames < UPLINK_MAX_INFO_FRAMES) && (pos+2 <= total_len) {
 | 
						|
		data := app_data[pos:]
 | 
						|
		frame_length := (uint32(data[0]) << 1) | (uint32(data[1]) >> 7)
 | 
						|
		frame_type := uint32(data[1]) & 0x0f
 | 
						|
		if pos+int(frame_length) > total_len {
 | 
						|
			break // Overrun?
 | 
						|
		}
 | 
						|
 | 
						|
		if frame_length == 0 { // Empty frame. Quit here.
 | 
						|
			break
 | 
						|
		}
 | 
						|
 | 
						|
		pos = pos + 2
 | 
						|
 | 
						|
		data = data[2 : frame_length+2]
 | 
						|
 | 
						|
		thisFrame := new(UATFrame)
 | 
						|
		thisFrame.Raw_data = data
 | 
						|
		thisFrame.frame_length = frame_length
 | 
						|
		thisFrame.Frame_type = frame_type
 | 
						|
 | 
						|
		thisFrame.decodeInfoFrame()
 | 
						|
 | 
						|
		// Save the decoded frame.
 | 
						|
		u.Frames = append(u.Frames, thisFrame)
 | 
						|
 | 
						|
		pos = pos + int(frame_length)
 | 
						|
	}
 | 
						|
 | 
						|
	u.decoded = true
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
	Aggregate all of the text rates across the frames in the message and return as an array.
 | 
						|
*/
 | 
						|
 | 
						|
func (u *UATMsg) GetTextReports() ([]string, error) {
 | 
						|
	ret := make([]string, 0)
 | 
						|
	if !u.decoded {
 | 
						|
		err := u.DecodeUplink()
 | 
						|
		if err != nil {
 | 
						|
			return ret, err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	for _, f := range u.Frames {
 | 
						|
		for _, m := range f.Text_data {
 | 
						|
			if len(m) > 0 {
 | 
						|
				ret = append(ret, m)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return ret, nil
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
	Parse out the message from the "dump978" output format.
 | 
						|
*/
 | 
						|
 | 
						|
func New(buf string) (*UATMsg, error) {
 | 
						|
	ret := new(UATMsg)
 | 
						|
 | 
						|
	buf = strings.Trim(buf, "\r\n") // Remove newlines.
 | 
						|
	x := strings.Split(buf, ";")    // We want to discard everything before the first ';'.
 | 
						|
 | 
						|
	if len(x) < 2 {
 | 
						|
		return ret, errors.New(fmt.Sprintf("New UATMsg: Invalid format (%s).", buf))
 | 
						|
	}
 | 
						|
 | 
						|
	/*
 | 
						|
		Parse _;rs=?;ss=? - if available.
 | 
						|
			RS_Err         int
 | 
						|
			SignalStrength int
 | 
						|
	*/
 | 
						|
	ret.SignalStrength = -1
 | 
						|
	ret.RS_Err = -1
 | 
						|
	for _, f := range x[1:] {
 | 
						|
		x2 := strings.Split(f, "=")
 | 
						|
		if len(x2) != 2 {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		i, err := strconv.Atoi(x2[1])
 | 
						|
		if err != nil {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		if x2[0] == "ss" {
 | 
						|
			ret.SignalStrength = i
 | 
						|
		} else if x2[0] == "rs" {
 | 
						|
			ret.RS_Err = i
 | 
						|
		}
 | 
						|
	}
 | 
						|
	s := x[0]
 | 
						|
 | 
						|
	// Only want "long" uplink messages.
 | 
						|
	if (len(s)-1)%2 != 0 || (len(s)-1)/2 != UPLINK_FRAME_DATA_BYTES {
 | 
						|
		return ret, errors.New(fmt.Sprintf("New UATMsg: short read (%d).", len(s)))
 | 
						|
	}
 | 
						|
 | 
						|
	if s[0] != '+' { // Only want + ("Uplink") messages currently. - (Downlink) or messages that start with other are discarded.
 | 
						|
		return ret, errors.New("New UATMsg: expecting uplink frame.")
 | 
						|
	}
 | 
						|
 | 
						|
	s = s[1:] // Remove the preceding '+' or '-' character.
 | 
						|
 | 
						|
	// Convert the hex string into a byte array.
 | 
						|
	frame := make([]byte, UPLINK_FRAME_DATA_BYTES)
 | 
						|
	hex.Decode(frame, []byte(s))
 | 
						|
	ret.msg = frame
 | 
						|
 | 
						|
	return ret, nil
 | 
						|
}
 |