parent
a932b425dc
commit
08ca4f386b
@ -0,0 +1,349 @@ |
||||
<!DOCTYPE html> |
||||
<html lang="en"> |
||||
<head> |
||||
<meta charset="UTF-8"> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||
<title>Flymaster GPS Client</title> |
||||
<!-- Vue.js --> |
||||
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script> |
||||
<!-- Flymaster Client --> |
||||
<script src="flymaster-client.js"></script> |
||||
<style> |
||||
body { |
||||
font-family: Arial, sans-serif; |
||||
max-width: 800px; |
||||
margin: 0 auto; |
||||
padding: 20px; |
||||
} |
||||
.container { |
||||
border: 1px solid #ddd; |
||||
border-radius: 5px; |
||||
padding: 20px; |
||||
margin-bottom: 20px; |
||||
} |
||||
h1, h2 { |
||||
color: #333; |
||||
} |
||||
button { |
||||
background-color: #4CAF50; |
||||
border: none; |
||||
color: white; |
||||
padding: 10px 15px; |
||||
text-align: center; |
||||
text-decoration: none; |
||||
display: inline-block; |
||||
font-size: 14px; |
||||
margin: 4px 2px; |
||||
cursor: pointer; |
||||
border-radius: 4px; |
||||
} |
||||
button:disabled { |
||||
background-color: #cccccc; |
||||
cursor: not-allowed; |
||||
} |
||||
.status { |
||||
margin: 10px 0; |
||||
padding: 10px; |
||||
border-radius: 4px; |
||||
} |
||||
.status.success { |
||||
background-color: #dff0d8; |
||||
color: #3c763d; |
||||
} |
||||
.status.error { |
||||
background-color: #f2dede; |
||||
color: #a94442; |
||||
} |
||||
.status.info { |
||||
background-color: #d9edf7; |
||||
color: #31708f; |
||||
} |
||||
table { |
||||
width: 100%; |
||||
border-collapse: collapse; |
||||
margin: 15px 0; |
||||
} |
||||
table, th, td { |
||||
border: 1px solid #ddd; |
||||
} |
||||
th, td { |
||||
padding: 8px; |
||||
text-align: left; |
||||
} |
||||
th { |
||||
background-color: #f2f2f2; |
||||
} |
||||
tr:nth-child(even) { |
||||
background-color: #f9f9f9; |
||||
} |
||||
.progress-container { |
||||
width: 100%; |
||||
background-color: #f1f1f1; |
||||
border-radius: 4px; |
||||
margin: 10px 0; |
||||
} |
||||
.progress-bar { |
||||
height: 20px; |
||||
background-color: #4CAF50; |
||||
border-radius: 4px; |
||||
text-align: center; |
||||
color: white; |
||||
line-height: 20px; |
||||
} |
||||
.track-list { |
||||
max-height: 300px; |
||||
overflow-y: auto; |
||||
} |
||||
</style> |
||||
</head> |
||||
<body> |
||||
<div id="app"> |
||||
<h1>Flymaster GPS Client</h1> |
||||
|
||||
<div class="container"> |
||||
<h2>Connection</h2> |
||||
<button @click="connect" :disabled="isConnected">Connect</button> |
||||
<button @click="disconnect" :disabled="!isConnected">Disconnect</button> |
||||
|
||||
<div v-if="statusMessage" :class="['status', statusType]"> |
||||
{{ statusMessage }} |
||||
</div> |
||||
|
||||
<div v-if="deviceInfo"> |
||||
<h3>Device Information</h3> |
||||
<p><strong>Name:</strong> {{ deviceInfo.name }}</p> |
||||
<p><strong>Unit ID:</strong> {{ deviceInfo.unitId }}</p> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="container" v-if="isConnected"> |
||||
<h2>Tracks</h2> |
||||
<button @click="refreshTracks" :disabled="isLoading">Refresh Track List</button> |
||||
|
||||
<div v-if="isLoading" class="status info"> |
||||
Loading tracks... |
||||
</div> |
||||
|
||||
<div v-if="tracks.length > 0" class="track-list"> |
||||
<table> |
||||
<thead> |
||||
<tr> |
||||
<th>Select</th> |
||||
<th>#</th> |
||||
<th>Start Date</th> |
||||
<th>End Date</th> |
||||
<th>Duration</th> |
||||
</tr> |
||||
</thead> |
||||
<tbody> |
||||
<tr v-for="track in tracks" :key="track.index"> |
||||
<td><input type="checkbox" v-model="selectedTracks" :value="track.index"></td> |
||||
<td>{{ track.index + 1 }}</td> |
||||
<td>{{ formatDate(track.startDate) }}</td> |
||||
<td>{{ formatDate(track.endDate) }}</td> |
||||
<td>{{ formatDuration(track.duration) }}</td> |
||||
</tr> |
||||
</tbody> |
||||
</table> |
||||
|
||||
<button @click="downloadTracks" :disabled="selectedTracks.length === 0 || isDownloading"> |
||||
Download Selected Tracks |
||||
</button> |
||||
</div> |
||||
|
||||
<div v-if="isDownloading"> |
||||
<h3>Download Progress</h3> |
||||
<div class="progress-container"> |
||||
<div class="progress-bar" :style="{ width: downloadProgress + '%' }"> |
||||
{{ downloadProgress }}% |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="container" v-if="trackPoints.length > 0"> |
||||
<h2>Downloaded Track Data</h2> |
||||
<p>Total points: {{ trackPoints.length }}</p> |
||||
|
||||
<div v-if="trackPoints.length > 0"> |
||||
<h3>Sample Points</h3> |
||||
<table> |
||||
<thead> |
||||
<tr> |
||||
<th>#</th> |
||||
<th>Latitude</th> |
||||
<th>Longitude</th> |
||||
<th>GPS Alt</th> |
||||
<th>Baro Alt</th> |
||||
<th>Time</th> |
||||
</tr> |
||||
</thead> |
||||
<tbody> |
||||
<tr v-for="(point, index) in samplePoints" :key="index"> |
||||
<td>{{ index + 1 }}</td> |
||||
<td>{{ point.lat.toFixed(6) }}</td> |
||||
<td>{{ point.lon.toFixed(6) }}</td> |
||||
<td>{{ point.gpsalt }}m</td> |
||||
<td>{{ point.baroalt.toFixed(1) }}m</td> |
||||
<td>{{ formatDate(new Date(point.time * 1000)) }}</td> |
||||
</tr> |
||||
</tbody> |
||||
</table> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
<script> |
||||
new Vue({ |
||||
el: '#app', |
||||
data: { |
||||
client: null, |
||||
isConnected: false, |
||||
isLoading: false, |
||||
isDownloading: false, |
||||
statusMessage: '', |
||||
statusType: 'info', |
||||
deviceInfo: null, |
||||
tracks: [], |
||||
selectedTracks: [], |
||||
trackPoints: [], |
||||
downloadProgress: 0 |
||||
}, |
||||
computed: { |
||||
samplePoints() { |
||||
// Return a sample of points (first 10) |
||||
return this.trackPoints.slice(0, 10); |
||||
} |
||||
}, |
||||
methods: { |
||||
async connect() { |
||||
try { |
||||
this.statusMessage = 'Connecting to device...'; |
||||
this.statusType = 'info'; |
||||
|
||||
this.client = new FlymasterClient(); |
||||
const connected = await this.client.connect(); |
||||
|
||||
if (connected) { |
||||
this.isConnected = true; |
||||
this.statusMessage = 'Connected to device successfully!'; |
||||
this.statusType = 'success'; |
||||
|
||||
// Initialize GPS |
||||
await this.initializeGps(); |
||||
} else { |
||||
this.statusMessage = 'Failed to connect to device.'; |
||||
this.statusType = 'error'; |
||||
} |
||||
} catch (error) { |
||||
console.error('Connection error:', error); |
||||
this.statusMessage = 'Error connecting to device: ' + error.message; |
||||
this.statusType = 'error'; |
||||
} |
||||
}, |
||||
|
||||
async disconnect() { |
||||
try { |
||||
if (this.client) { |
||||
await this.client.disconnect(); |
||||
this.isConnected = false; |
||||
this.statusMessage = 'Disconnected from device.'; |
||||
this.statusType = 'info'; |
||||
this.deviceInfo = null; |
||||
} |
||||
} catch (error) { |
||||
console.error('Disconnection error:', error); |
||||
this.statusMessage = 'Error disconnecting from device: ' + error.message; |
||||
this.statusType = 'error'; |
||||
} |
||||
}, |
||||
|
||||
async initializeGps() { |
||||
try { |
||||
this.isLoading = true; |
||||
this.statusMessage = 'Initializing GPS...'; |
||||
this.statusType = 'info'; |
||||
|
||||
await this.client.initGps(); |
||||
|
||||
this.deviceInfo = { |
||||
name: this.client.gpsname, |
||||
unitId: this.client.gpsunitid |
||||
}; |
||||
|
||||
this.refreshTracks(); |
||||
|
||||
this.statusMessage = 'GPS initialized successfully!'; |
||||
this.statusType = 'success'; |
||||
} catch (error) { |
||||
console.error('GPS initialization error:', error); |
||||
this.statusMessage = 'Error initializing GPS: ' + error.message; |
||||
this.statusType = 'error'; |
||||
} finally { |
||||
this.isLoading = false; |
||||
} |
||||
}, |
||||
|
||||
async refreshTracks() { |
||||
try { |
||||
this.isLoading = true; |
||||
this.statusMessage = 'Loading tracks...'; |
||||
this.statusType = 'info'; |
||||
|
||||
this.tracks = this.client.getTrackList(); |
||||
this.selectedTracks = []; |
||||
|
||||
this.statusMessage = `Loaded ${this.tracks.length} tracks.`; |
||||
this.statusType = 'success'; |
||||
} catch (error) { |
||||
console.error('Track loading error:', error); |
||||
this.statusMessage = 'Error loading tracks: ' + error.message; |
||||
this.statusType = 'error'; |
||||
} finally { |
||||
this.isLoading = false; |
||||
} |
||||
}, |
||||
|
||||
async downloadTracks() { |
||||
if (this.selectedTracks.length === 0) return; |
||||
|
||||
try { |
||||
this.isDownloading = true; |
||||
this.downloadProgress = 0; |
||||
this.statusMessage = 'Downloading selected tracks...'; |
||||
this.statusType = 'info'; |
||||
|
||||
this.client.selectTracks(this.selectedTracks); |
||||
|
||||
this.trackPoints = await this.client.downloadSelectedTracks((current, total) => { |
||||
this.downloadProgress = Math.floor((current / total) * 100); |
||||
return true; // Continue download |
||||
}); |
||||
|
||||
this.statusMessage = `Downloaded ${this.trackPoints.length} points successfully!`; |
||||
this.statusType = 'success'; |
||||
} catch (error) { |
||||
console.error('Download error:', error); |
||||
this.statusMessage = 'Error downloading tracks: ' + error.message; |
||||
this.statusType = 'error'; |
||||
} finally { |
||||
this.isDownloading = false; |
||||
} |
||||
}, |
||||
|
||||
formatDate(date) { |
||||
return new Date(date).toLocaleString(); |
||||
}, |
||||
|
||||
formatDuration(seconds) { |
||||
const hours = Math.floor(seconds / 3600); |
||||
const minutes = Math.floor((seconds % 3600) / 60); |
||||
const secs = seconds % 60; |
||||
|
||||
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; |
||||
} |
||||
} |
||||
}); |
||||
</script> |
||||
</body> |
||||
</html> |
Loading…
Reference in new issue