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