krz/moonrise

Night-sky companion for iPhone, Apple Watch and Vision Pro: what's up tonight, the Moon, planets, launches and missions, and a Library of the solar system. SwiftUI app with a Cloudflare Workers backend. astronomy cloudflare-workers ios night-sky space swiftui visionos watchos

Commit b06dffc6a3

b06dffc6a373d161888cfa9da302d6ade42c25d4

parent: ddb85068e2

Unsigned

cmc <hello@cleberg.net> · 2026-10-18 08:37 UTC

v2: Deep Space Network view (!22)

- =Feeds.DSNStatus= decodes =eyes.nasa.gov/dsn/data/dsn.xml=; names from =config.xml= (bundled table).
- System tab section: per station, the spacecraft in contact, direction (receiving, sending, two-way), downlink rate, round-trip time; 30 s refresh.

Tests against a saved snapshot. Checked live in the simulator (Voyager 2 at 160 b/s via Canberra).

Layout: unified · split

App/Sources/DSNSection.swift added +71
@@ -0,0 +1,71 @@
1import Feeds
2import SwiftUI
3
4/// Which Deep Space Network dishes are talking to which spacecraft right now.
5struct DSNSection: View {
6 var ink: Color
7 var accent: Color
8 @State private var status: DSNStatus?
9 @State private var failed = false
10
11 private var activeDishes: [DSNStatus.Dish] {
12 status?.dishes.filter { !$0.links.isEmpty } ?? []
13 }
14
15 var body: some View {
16 VStack(alignment: .leading, spacing: 10) {
17 Text("Deep Space Network now").font(.caption2.weight(.medium)).textCase(.uppercase).tracking(0.8).dimmed(0.6)
18 if let status {
19 ForEach(["Goldstone", "Madrid", "Canberra"], id: \.self) { station in
20 let dishes = activeDishes.filter { $0.station == station }
21 VStack(alignment: .leading, spacing: 4) {
22 Text(station).font(.subheadline.weight(.medium))
23 if dishes.isEmpty {
24 Text("No spacecraft in contact").font(.footnote).dimmed(0.6)
25 }
26 ForEach(dishes) { dish in
27 ForEach(dish.links, id: \.self) { link in
28 HStack(alignment: .firstTextBaseline) {
29 Text(link.spacecraft).font(.footnote)
30 Spacer()
31 Text(describe(link, dish: dish)).font(.footnote.monospacedDigit()).dimmed(0.7)
32 }
33 .accessibilityElement(children: .combine)
34 }
35 }
36 }
37 .padding(.vertical, 4)
38 }
39 Text("Updated \(status.updated.formatted(date: .omitted, time: .shortened)). Data: NASA/JPL Deep Space Network.")
40 .font(.caption2).dimmed(0.5)
41 } else if failed {
42 Text("Couldn't load Deep Space Network status.").font(.footnote).foregroundStyle(accent)
43 } else {
44 ProgressView().tint(ink)
45 }
46 }
47 .task {
48 while !Task.isCancelled {
49 do { status = try await DSNStatus.fetch(); failed = false } catch { failed = status == nil }
50 try? await Task.sleep(for: .seconds(30))
51 }
52 }
53 }
54
55 private func describe(_ l: DSNStatus.Link, dish: DSNStatus.Dish) -> String {
56 var parts = [dish.name.replacingOccurrences(of: "DSS", with: "DSS ")]
57 if l.receiving && l.sending { parts.append("two-way") } else if l.receiving { parts.append("receiving") } else { parts.append("sending") }
58 if let r = l.downlinkRate { parts.append(rate(r)) }
59 if let rt = l.roundTrip, rt > 60 { parts.append("round trip \(duration(rt))") }
60 return parts.joined(separator: " · ")
61 }
62
63 private func rate(_ bps: Double) -> String {
64 bps >= 1_000_000 ? String(format: "%.1f Mb/s", bps / 1_000_000) : bps >= 1000 ? String(format: "%.0f kb/s", bps / 1000) : String(format: "%.0f b/s", bps)
65 }
66
67 private func duration(_ s: Double) -> String {
68 let h = Int(s / 3600), m = Int(s.truncatingRemainder(dividingBy: 3600) / 60)
69 return h > 0 ? "\(h) h \(m) m" : "\(m) m"
70 }
71}
App/Sources/SettingsScreen.swift +1
@@ -143,6 +143,7 @@ struct CreditsScreen: View {
143 Credit(name: "Space weather and aurora forecast", detail: "NOAA Space Weather Prediction Center."), 143 Credit(name: "Space weather and aurora forecast", detail: "NOAA Space Weather Prediction Center."),
144 Credit(name: "Images of the Sun", detail: "Courtesy of NASA/SDO and the AIA, EVE, and HMI science teams."), 144 Credit(name: "Images of the Sun", detail: "Courtesy of NASA/SDO and the AIA, EVE, and HMI science teams."),
145 Credit(name: "Launch schedule", detail: "The Space Devs, Launch Library 2."), 145 Credit(name: "Launch schedule", detail: "The Space Devs, Launch Library 2."),
146 Credit(name: "Deep Space Network status", detail: "NASA/JPL, DSN Now."),
146 ] 147 ]
147 148
148 /// Every distinct image credit used by the Library, grouped by licence. 149 /// Every distinct image credit used by the Library, grouped by licence.
App/Sources/SystemScreen.swift +1
@@ -87,6 +87,7 @@ struct SystemScreen: View {
87 } 87 }
88 } 88 }
89 } 89 }
90 DSNSection(ink: ink, accent: accent).padding(.top, 24)
90 Text("Spacecraft positions: NASA/JPL Horizons. Pioneer 10 and 11 positions after contact was lost are predictions.") 91 Text("Spacecraft positions: NASA/JPL Horizons. Pioneer 10 and 11 positions after contact was lost are predictions.")
91 .font(.caption2).dimmed(0.5).padding(.vertical, 16) 92 .font(.caption2).dimmed(0.5).padding(.vertical, 16)
92 } 93 }
Packages/MoonriseKit/Package.swift +1 −1
@@ -26,7 +26,7 @@ let package = Package(
26 .target(name: "Feeds"), 26 .target(name: "Feeds"),
27 .target(name: "Library", resources: [.copy("Resources/pages")]), 27 .target(name: "Library", resources: [.copy("Resources/pages")]),
28 .testTarget(name: "SkyEngineTests", dependencies: ["SkyEngine"]), 28 .testTarget(name: "SkyEngineTests", dependencies: ["SkyEngine"]),
29 .testTarget(name: "FeedsTests", dependencies: ["Feeds"]), 29 .testTarget(name: "FeedsTests", dependencies: ["Feeds"], resources: [.copy("Fixtures")]),
30 .testTarget(name: "LibraryTests", dependencies: ["Library", "SkyRenderer", "SkyEngine"]), 30 .testTarget(name: "LibraryTests", dependencies: ["Library", "SkyRenderer", "SkyEngine"]),
31 .testTarget(name: "SkyRendererTests", dependencies: ["SkyRenderer"]), 31 .testTarget(name: "SkyRendererTests", dependencies: ["SkyRenderer"]),
32 ] 32 ]
Packages/MoonriseKit/Sources/Feeds/DSN.swift added +95
@@ -0,0 +1,95 @@
1import Foundation
2#if canImport(FoundationXML)
3import FoundationXML
4#endif
5
6/// NASA's Deep Space Network Now feed: what each antenna is talking to.
7public struct DSNStatus: Sendable {
8 public struct Link: Sendable, Hashable {
9 /// Display name, e.g. "Voyager 1".
10 public var spacecraft: String
11 public var receiving: Bool
12 public var sending: Bool
13 /// Bits per second of the active downlink, if any.
14 public var downlinkRate: Double?
15 /// Round-trip light time in seconds, if reported.
16 public var roundTrip: Double?
17 }
18
19 public struct Dish: Sendable, Identifiable {
20 public var name: String
21 public var station: String
22 public var activity: String
23 public var links: [Link]
24 public var id: String { name }
25 }
26
27 public var updated: Date
28 public var dishes: [Dish]
29
30 public static let url = URL(string: "https://eyes.nasa.gov/dsn/data/dsn.xml")!
31
32 public static func fetch(session: URLSession = .shared) async throws -> DSNStatus {
33 try decode(await session.data(from: url).0)
34 }
35
36 public static func decode(_ data: Data) throws -> DSNStatus {
37 let p = Parser()
38 let xml = XMLParser(data: data)
39 xml.delegate = p
40 guard xml.parse() else { throw xml.parserError ?? CocoaError(.coderReadCorrupt) }
41 return DSNStatus(updated: p.updated, dishes: p.dishes)
42 }
43
44 public static func displayName(_ code: String) -> String {
45 dsnSpacecraftNames[code.lowercased()] ?? code
46 }
47
48 final class Parser: NSObject, XMLParserDelegate {
49 var updated = Date.distantPast
50 var dishes: [Dish] = []
51 var station = ""
52 var current: Dish?
53 var signals: [String: (down: Bool, up: Bool, rate: Double?)] = [:]
54 var rtlt: [String: Double] = [:]
55
56 func parser(_ parser: XMLParser, didStartElement name: String, namespaceURI: String?, qualifiedName: String?, attributes a: [String: String]) {
57 switch name {
58 case "station":
59 station = a["friendlyName"] ?? a["name"] ?? ""
60 if let ms = a["timeUTC"].flatMap(Double.init) { updated = Date(timeIntervalSince1970: ms / 1000) }
61 case "dish":
62 current = Dish(name: a["name"] ?? "", station: station, activity: a["activity"] ?? "", links: [])
63 signals = [:]
64 rtlt = [:]
65 case "downSignal", "upSignal":
66 guard let sc = a["spacecraft"], !sc.isEmpty else { return }
67 let active = a["active"] == "true"
68 var s = signals[sc] ?? (false, false, nil)
69 if name == "downSignal" {
70 s.down = s.down || active
71 if active, let r = a["dataRate"].flatMap(Double.init), r > 0 { s.rate = r }
72 } else {
73 s.up = s.up || active
74 }
75 signals[sc] = s
76 case "target":
77 if let n = a["name"], let t = a["rtlt"].flatMap(Double.init), t > 0 { rtlt[n] = t }
78 default:
79 break
80 }
81 }
82
83 func parser(_ parser: XMLParser, didEndElement name: String, namespaceURI: String?, qualifiedName: String?) {
84 guard name == "dish", var dish = current else { return }
85 dish.links = signals
86 .filter { $0.value.down || $0.value.up }
87 .map { code, s in
88 Link(spacecraft: DSNStatus.displayName(code), receiving: s.down, sending: s.up, downlinkRate: s.rate, roundTrip: rtlt[code])
89 }
90 .sorted { $0.spacecraft < $1.spacecraft }
91 dishes.append(dish)
92 current = nil
93 }
94 }
95}
Packages/MoonriseKit/Sources/Feeds/DSNNames.swift added +173
@@ -0,0 +1,173 @@
1/// Spacecraft display names from NASA's DSN Now configuration (eyes.nasa.gov/dsn/config.xml).
2let dsnSpacecraftNames: [String: String] = [
3 "ace": "Advanced Composition Explorer",
4 "agm1": "Astrobotic Griffin Lander",
5 "apm1": "Astrobotic Peregrine 1 Mission",
6 "argo": "ArgoMoon",
7 "atot": "Advanced Tracking and Observational Techniques",
8 "bepi": "BepiColombo",
9 "bios": "BioSentinel",
10 "caps": "CAPSTONE",
11 "cas": "Cassini",
12 "cass": "Testing",
13 "cgo": "Carruthers Geocorona Observatory",
14 "ch1": "Chandrayaan",
15 "ch2": "Chandrayaan-2",
16 "ch2l": "Chandrayaan-2 Lander",
17 "ch2o": "Chandrayaan-2 Orbiter",
18 "ch3": "Chandrayaan-3",
19 "chdr": "Chandra",
20 "clu1": "Cluster 1",
21 "clu2": "Cluster 2",
22 "clu3": "Cluster 3",
23 "clu4": "Cluster 4",
24 "cue3": "CU Earth Escape Explorer",
25 "cusp": "CubeSat for Solar Particles",
26 "dart": "Double Asteroid Redirection Test",
27 "dawn": "Dawn",
28 "dif": "Deep Impact",
29 "dsco": "Deep Space Climate Observatory (DSCOVR)",
30 "dsn": "Testing",
31 "egs": "EVN and Global Services",
32 "em-1": "Exploration Mission-1",
33 "em-10": "Exploration Mission-10",
34 "em-2": "Exploration Mission-2",
35 "em-3": "Exploration Mission-3",
36 "em-4": "Exploration Mission-4",
37 "em-5": "Exploration Mission-5",
38 "em-6": "Exploration Mission-6",
39 "em-7": "Exploration Mission-7",
40 "em-8": "Exploration Mission-8",
41 "em-9": "Exploration Mission-9",
42 "em1": "Human Space Flight: Artemis I",
43 "em2": "Human Space Flight: Artemis II",
44 "em3": "Human Space Flight: Artemis III",
45 "emm": "Emirates Mars Mission (Hope)",
46 "equl": "EQUULEUS",
47 "escb": "ESCAPADE Blue",
48 "escg": "ESCAPADE Gold",
49 "eurc": "Europa Clipper",
50 "eus": "Exploration Upper Stage",
51 "gaia": "Gaia",
52 "gbra": "Ground Based Radio Astronomy",
53 "go10": "GOES 10",
54 "go11": "GOES 11",
55 "go12": "GOES 12",
56 "go13": "GOES 13",
57 "go14": "GOES 14",
58 "go15": "GOES 15",
59 "go16": "GOES 16",
60 "go17": "GOES 17",
61 "grla": "GRAIL A",
62 "grlb": "GRAIL B",
63 "gssr": "Goldstone Solar System Radar",
64 "gtl": "Geotail",
65 "gvrt": "Goldstone Apple Valley Radio Telescope",
66 "hcra": "Host Country Radio Astronomy",
67 "hmap": "LunaH-Map",
68 "hst": "Hubble Space Telescope",
69 "hyb2": "Hayabusa-2",
70 "ice": "ISEE-3 Reboot",
71 "icps": "Interim Cryogenic Propulsion Stage",
72 "imag": "IMAGE",
73 "imap": "IMAP",
74 "intg": "INTEGRAL",
75 "jno": "Juno",
76 "jnsa": "Janus A",
77 "jnsb": "Janus B",
78 "jwst": "James Webb Space Telescope",
79 "kepl": "Kepler",
80 "kplo": "Korea Pathfinder Lunar Orbiter",
81 "lade": "LADEE",
82 "lcro": "LCROSS",
83 "lfl": "Lunar Flashlight",
84 "lici": "LICIACube",
85 "lnd1": "Lunar Node 1",
86 "lro": "Lunar Reconnaissance Orbiter",
87 "ltb": "Lunar Trailblazer",
88 "ltst": "Interim Cryogenic Propulsion Stage",
89 "lucy": "Lucy",
90 "lunah-map": "Lunar Hydrogen MAPper",
91 "m01o": "Mars Odyssey",
92 "m01s": "Mars Odyssey",
93 "m20": "Mars 2020 (Perseverance)",
94 "map": "WMAP",
95 "mcoa": "MarCO A",
96 "mcob": "MarCO B",
97 "mer1": "Opportunity",
98 "mer2": "Spirit",
99 "mex": "Mars Express",
100 "mgs": "Mars Global Surveyor",
101 "mlic": "Lunar IceCube",
102 "mms1": "MMS 1",
103 "mms2": "MMS 2",
104 "mms3": "MMS 3",
105 "mms4": "MMS 4",
106 "mmx": "MMX",
107 "mom": "Mars Orbiter Mission",
108 "mro": "Mars Reconnaissance Orbiter",
109 "mros": "Mars Reconnaissance Orbiter",
110 "msgr": "MESSENGER",
111 "msl": "Mars Science Laboratory (Curiosity)",
112 "musc": "Hayabusa",
113 "mvn": "MAVEN",
114 "neas": "Near Earth Asteroid Scout",
115 "nhpc": "New Horizons",
116 "no15": "NOAA 15",
117 "no16": "NOAA 16",
118 "no17": "NOAA 17",
119 "no18": "NOAA 18",
120 "nsyt": "InSight",
121 "olin": "SWARM-EX",
122 "omot": "OMOTENASHI",
123 "orx": "OSIRIS-REx",
124 "phx": "Phoenix",
125 "plc": "Akatsuki",
126 "polr": "POLAR",
127 "psyc": "Psyche",
128 "rd1": "Red Dragon 1",
129 "rfc": "Reference Frame Calibration",
130 "rose": "Rosetta",
131 "rp": "Resource Prospector",
132 "rsp": "ExoMars Rover and Surface Platform",
133 "rst": "Roman Space Telescope",
134 "rsts": "Resource System Time Sharing",
135 "sdu": "Stardust",
136 "sele": "Kaguya",
137 "sgp": "Space Geodesy Program",
138 "slim": "Smart Lander for Investigating Moon",
139 "soho": "SOHO",
140 "spil": "Beresheet",
141 "spp": "Parker Solar Probe",
142 "sta": "STEREO A",
143 "stab": "STEREO B",
144 "stb": "STEREO B",
145 "stf": "Spitzer Space Telescope",
146 "swfo": "Space Weather Follow On-Lagrange 1",
147 "td10": "TDRS 10",
148 "td11": "TDRS 11",
149 "td12": "TDRS 12",
150 "td13": "TDRS 13",
151 "tdr1": "TDRS 1",
152 "tdr3": "TDRS 3",
153 "tdr4": "TDRS 4",
154 "tdr5": "TDRS 5",
155 "tdr6": "TDRS 6",
156 "tdr7": "TDRS 7",
157 "tdr8": "TDRS 8",
158 "tdr9": "TDRS 9",
159 "terr": "TERRA",
160 "tess": "Transiting Exoplanet Survey Satellite",
161 "tgo": "Trace Gas Orbiter",
162 "thb": "THEMIS B",
163 "thc": "THEMIS C",
164 "tm": "Team Miles",
165 "tmm": "Team Miles",
166 "ulys": "Ulysses",
167 "vex": "Venus Express",
168 "vgr1": "Voyager 1",
169 "vgr2": "Voyager 2",
170 "vlbi": "V.L.B. Interferometry",
171 "wind": "WIND",
172 "xmm": "X-ray Multi-Mirror Mission (XMM-Newton)",
173]
Packages/MoonriseKit/Tests/FeedsTests/DSNTests.swift added +32
@@ -0,0 +1,32 @@
1import Foundation
2import Testing
3@testable import Feeds
4
5@Test func decodesLiveDSNSnapshot() throws {
6 // dsn.xml saved from eyes.nasa.gov/dsn/data/dsn.xml on 2026-10-18.
7 let url = try #require(Bundle.module.url(forResource: "Fixtures/dsn", withExtension: "xml"))
8 let s = try DSNStatus.decode(Data(contentsOf: url))
9 #expect(s.dishes.count == 15)
10 #expect(Set(s.dishes.map(\.station)) == ["Goldstone", "Madrid", "Canberra"])
11 #expect(s.updated > Date(timeIntervalSince1970: 1_790_000_000))
12 #expect(s.dishes.first { $0.name == "DSS14" }?.activity == "Engineering Upgrades")
13}
14
15@Test func linksNeedAnActiveSignal() throws {
16 let xml = Data("""
17 <dsn><station name="gdscc" friendlyName="Goldstone" timeUTC="1792312582000"/>
18 <dish name="DSS43" activity="Spacecraft Telemetry">
19 <downSignal active="true" dataRate="160" band="X" spacecraft="VGR2" spacecraftID="-32"/>
20 <upSignal active="false" spacecraft="VGR2" spacecraftID="-32"/>
21 <downSignal active="false" spacecraft="MRO" spacecraftID="-74"/>
22 <target name="VGR2" id="32" rtlt="138000"/>
23 </dish></dsn>
24 """.utf8)
25 let s = try DSNStatus.decode(xml)
26 let links = try #require(s.dishes.first?.links)
27 #expect(links.count == 1)
28 #expect(links[0].spacecraft == "Voyager 2")
29 #expect(links[0].receiving && !links[0].sending)
30 #expect(links[0].downlinkRate == 160)
31 #expect(links[0].roundTrip == 138000)
32}
Packages/MoonriseKit/Tests/FeedsTests/Fixtures/dsn.xml added +74
@@ -0,0 +1,74 @@
1<dsn>
2 <station name="gdscc" friendlyName="Goldstone" timeUTC="1792312582000" timeZoneOffset="-25200000.0"/>
3 <dish name="DSS25" azimuthAngle="97" elevationAngle="41" windSpeed="2" isMSPA="false" isArray="false" isDDOR="false" activity="DSN Very Long Baseline Interferometry">
4 <target name="DSN" id="99" uplegRange="-1" downlegRange="-1" rtlt="-1"/>
5 </dish>
6 <dish name="DSS14" azimuthAngle="0" elevationAngle="90" windSpeed="" isMSPA="false" isArray="false" isDDOR="false" activity="Engineering Upgrades">
7 <target name="DSN" id="99" uplegRange="-1" downlegRange="-1" rtlt="-1"/>
8 </dish>
9 <dish name="DSS24" azimuthAngle="72" elevationAngle="7" windSpeed="2" isMSPA="false" isArray="false" isDDOR="false" activity="Spacecraft Telemetry, Tracking, and Command">
10 <downSignal active="false" signalType="none" dataRate="0" frequency="0" band="X" power="-140" spacecraft="MRO" spacecraftID="-74"/>
11 <upSignal active="false" signalType="none" dataRate="0" frequency="0" band="X" power="0" spacecraft="MRO" spacecraftID="-74"/>
12 <downSignal active="false" signalType="none" dataRate="0" frequency="0" band="X" power="-150" spacecraft="TGO" spacecraftID="-143"/>
13 <target name="MRO" id="74" uplegRange="-1" downlegRange="-1" rtlt="-1"/>
14 <target name="TGO" id="143" uplegRange="-1" downlegRange="-1" rtlt="-1"/>
15 </dish>
16 <dish name="DSS23" azimuthAngle="72" elevationAngle="7" windSpeed="0" isMSPA="false" isArray="false" isDDOR="false" activity="Spacecraft Telemetry, Tracking, and Command">
17 <upSignal active="false" signalType="none" dataRate="0" frequency="0" band="S" power="0" spacecraft="M01O" spacecraftID="-53"/>
18 <downSignal active="false" signalType="none" dataRate="0" frequency="0" band="X" power="-480" spacecraft="M01O" spacecraftID="-53"/>
19 <target name="M01O" id="53" uplegRange="-1" downlegRange="-1" rtlt="-1"/>
20 </dish>
21 <dish name="DSS26" azimuthAngle="249" elevationAngle="34" windSpeed="2" isMSPA="false" isArray="false" isDDOR="false" activity="Spacecraft Telemetry, Tracking, and Command">
22 <upSignal active="true" signalType="data" dataRate="0" frequency="0" band="S" power="0.2" spacecraft="MMS1" spacecraftID="-108"/>
23 <downSignal active="true" signalType="data" dataRate="0" frequency="0" band="S" power="-110" spacecraft="MMS1" spacecraftID="-108"/>
24 <target name="MMS1" id="108" uplegRange="146000" downlegRange="146000" rtlt="-1"/>
25 </dish>
26 <station name="mdscc" friendlyName="Madrid" timeUTC="1792312582000" timeZoneOffset="7200000.0"/>
27 <dish name="DSS55" azimuthAngle="0" elevationAngle="90" windSpeed="" isMSPA="false" isArray="false" isDDOR="false" activity="Engineering Upgrades">
28 <target name="DSN" id="99" uplegRange="-1" downlegRange="-1" rtlt="-1"/>
29 </dish>
30 <dish name="DSS63" azimuthAngle="0" elevationAngle="90" windSpeed="" isMSPA="false" isArray="false" isDDOR="false" activity="Engineering Upgrades">
31 <target name="DSN" id="99" uplegRange="-1" downlegRange="-1" rtlt="-1"/>
32 </dish>
33 <dish name="DSS54" azimuthAngle="302" elevationAngle="27" windSpeed="6" isMSPA="false" isArray="false" isDDOR="false" activity="Spacecraft Telemetry, Tracking, and Command">
34 <upSignal active="true" signalType="data" dataRate="0" frequency="0" band="S" power="2" spacecraft="TESS" spacecraftID="-95"/>
35 <downSignal active="false" signalType="none" dataRate="115400000" frequency="0" band="K" power="-400" spacecraft="TESS" spacecraftID="-95"/>
36 <downSignal active="true" signalType="data" dataRate="1000000" frequency="0" band="S" power="-110" spacecraft="TESS" spacecraftID="-95"/>
37 <target name="TESS" id="95" uplegRange="113000" downlegRange="113000" rtlt="-1"/>
38 </dish>
39 <dish name="DSS53" azimuthAngle="195" elevationAngle="63" windSpeed="6" isMSPA="false" isArray="false" isDDOR="false" activity="Spacecraft Telemetry, Tracking, and Command">
40 <upSignal active="true" signalType="data" dataRate="0" frequency="0" band="X" power="18" spacecraft="JNO" spacecraftID="-61"/>
41 <downSignal active="true" signalType="data" dataRate="25980" frequency="0" band="X" power="-130" spacecraft="JNO" spacecraftID="-61"/>
42 <target name="JNO" id="61" uplegRange="-1" downlegRange="-1" rtlt="-1"/>
43 </dish>
44 <dish name="DSS56" azimuthAngle="139" elevationAngle="42" windSpeed="6" isMSPA="false" isArray="false" isDDOR="false" activity="Spacecraft Telemetry, Tracking, and Command">
45 <upSignal active="true" signalType="data" dataRate="0" frequency="0" band="S" power="0.1" spacecraft="SOHO" spacecraftID="-21"/>
46 <downSignal active="true" signalType="data" dataRate="245800" frequency="0" band="S" power="-110" spacecraft="SOHO" spacecraftID="-21"/>
47 <target name="SOHO" id="21" uplegRange="-1" downlegRange="-1" rtlt="-1"/>
48 </dish>
49 <dish name="DSS65" azimuthAngle="80" elevationAngle="90" windSpeed="6" isMSPA="false" isArray="false" isDDOR="false" activity="Spacecraft Telemetry, Tracking, and Command">
50 <upSignal active="false" signalType="none" dataRate="0" frequency="0" band="X" power="0" spacecraft="STA" spacecraftID="-234"/>
51 <downSignal active="false" signalType="none" dataRate="0" frequency="0" band="X" power="-470" spacecraft="STA" spacecraftID="-234"/>
52 <target name="STA" id="234" uplegRange="-1" downlegRange="-1" rtlt="-1"/>
53 </dish>
54 <station name="cdscc" friendlyName="Canberra" timeUTC="1792312582000" timeZoneOffset="39600000.0"/>
55 <dish name="DSS34" azimuthAngle="0" elevationAngle="90" windSpeed="" isMSPA="false" isArray="false" isDDOR="false" activity="Spacecraft Telemetry, Tracking, and Command">
56 <downSignal active="true" signalType="data" dataRate="160" frequency="0" band="X" power="-160" spacecraft="VGR2" spacecraftID="-32"/>
57 <downSignal active="true" signalType="data" dataRate="0" frequency="0" band="X" power="-170" spacecraft="VGR2" spacecraftID="-32"/>
58 <target name="VGR2" id="32" uplegRange="21600000000" downlegRange="21600000000" rtlt="-1"/>
59 </dish>
60 <dish name="DSS35" azimuthAngle="182" elevationAngle="66" windSpeed="0" isMSPA="false" isArray="false" isDDOR="false" activity="Spacecraft Telemetry, Tracking, and Command">
61 <downSignal active="true" signalType="data" dataRate="0" frequency="0" band="X" power="-170" spacecraft="VGR2" spacecraftID="-32"/>
62 <target name="VGR2" id="32" uplegRange="21600000000" downlegRange="21600000000" rtlt="-1"/>
63 </dish>
64 <dish name="DSS43" azimuthAngle="261" elevationAngle="56" windSpeed="0" isMSPA="false" isArray="false" isDDOR="false" activity="Science Radar Astronomy">
65 <target name="GBRA" id="99" uplegRange="-1" downlegRange="-1" rtlt="-1"/>
66 </dish>
67 <dish name="DSS36" azimuthAngle="316" elevationAngle="75" windSpeed="0" isMSPA="false" isArray="false" isDDOR="false" activity="Spacecraft Telemetry, Tracking, and Command">
68 <upSignal active="true" signalType="data" dataRate="0" frequency="0" band="S" power="0.1" spacecraft="LRO" spacecraftID="-85"/>
69 <downSignal active="true" signalType="data" dataRate="146400" frequency="0" band="S" power="-110" spacecraft="LRO" spacecraftID="-85"/>
70 <downSignal active="true" signalType="data" dataRate="28590000" frequency="0" band="K" power="-90" spacecraft="LRO" spacecraftID="-85"/>
71 <target name="LRO" id="85" uplegRange="-1" downlegRange="-1" rtlt="-1"/>
72 </dish>
73 <timestamp>1792312582000</timestamp>
74</dsn>