CanSat V3: Discussions, Issues, And Solutions

by Alex Johnson 46 views

Introduction to CanSat V3

In this comprehensive article, we delve into the discussions surrounding CanSat V3, exploring various facets, from troubleshooting common issues to sharing valuable insights. CanSat projects are an incredible way to engage students and enthusiasts in hands-on space-related activities. CanSat V3, like its predecessors, presents a unique set of challenges and opportunities. Whether you're a seasoned participant or new to the field, understanding the intricacies of this project is crucial for success.

CanSat projects simulate the functionality of a real satellite within the volume and shape of a soda can. These projects challenge participants to design and build a fully functional satellite, incorporating various sensors, communication systems, and power sources. The CanSat V3 platform is a continuation of this tradition, encouraging innovation and practical application of engineering principles. One of the primary goals is to foster interest in STEM (Science, Technology, Engineering, and Mathematics) fields by providing a tangible and exciting project.

Throughout the project lifecycle, from initial design to final launch and data analysis, teams encounter numerous hurdles. These can range from hardware malfunctions and software bugs to communication issues and power management problems. Addressing these challenges effectively requires a collaborative approach, where team members share knowledge and learn from each other's experiences. This article aims to compile these discussions, offering a central repository of solutions, tips, and best practices for navigating the complexities of CanSat V3. By examining real-world issues and their resolutions, we hope to empower participants to tackle their own challenges with greater confidence and expertise.

Whether it's optimizing sensor performance, enhancing data transmission reliability, or refining deployment mechanisms, each aspect of the CanSat V3 project demands careful consideration and innovative solutions. The insights shared here are intended to provide a broad overview of the landscape, helping teams to anticipate potential pitfalls and develop robust strategies for success. Join us as we explore the discussions, issues, and solutions that define the CanSat V3 experience, and discover how collaborative problem-solving can lead to remarkable achievements.

Common Issues and Troubleshooting

Navigating the common issues encountered during a CanSat V3 project can be daunting, but with the right approach, troubleshooting becomes a manageable process. In this section, we will explore some of the most frequent problems and offer practical solutions. From hardware malfunctions to software glitches, understanding these issues is the first step towards resolving them effectively. Effective troubleshooting not only saves time but also enhances the overall learning experience, reinforcing critical thinking and problem-solving skills.

One of the most common challenges is power management. CanSat projects operate under strict size and weight constraints, which often limit the capacity of the power source. Ensuring a stable and sufficient power supply for all components requires careful planning and monitoring. Issues may arise from battery drain, voltage drops, or faulty connections. Solutions often involve optimizing power consumption by implementing sleep modes for less critical components, using more efficient power converters, and meticulously checking all wiring and connections. Regular testing of the power system under simulated flight conditions is crucial for identifying potential issues early on.

Another frequent area of concern is sensor performance. CanSats typically incorporate a variety of sensors to collect data on temperature, pressure, altitude, and other environmental parameters. Accurate and reliable sensor readings are essential for the mission's success. However, sensors can be sensitive to environmental conditions, electromagnetic interference, and calibration errors. Troubleshooting sensor issues often involves verifying sensor calibration, shielding sensitive components from interference, and implementing data filtering techniques to remove noise. Regularly comparing sensor readings with known benchmarks can help identify and correct inaccuracies.

Communication systems are also a significant source of potential problems. Establishing a reliable communication link between the CanSat and the ground station is vital for transmitting data and receiving commands. Issues may stem from antenna misalignment, signal interference, or software bugs in the communication protocol. Troubleshooting communication problems often requires optimizing antenna placement, using error-correction techniques, and ensuring that both the CanSat and the ground station are configured correctly. Conducting range tests before the actual launch can help identify and address communication limitations.

Software glitches can also impede the performance of a CanSat. The onboard software controls various functions, including data acquisition, processing, and transmission. Bugs in the code can lead to unexpected behavior, system crashes, or data loss. Debugging software issues often involves thorough code reviews, unit testing, and using debugging tools to identify and fix errors. Implementing robust error-handling routines can help prevent minor issues from escalating into major problems.

Finally, mechanical failures can occur, especially during deployment. The CanSat's deployment mechanism must function reliably to release the satellite without causing damage. Issues may arise from physical obstructions, motor malfunctions, or structural weaknesses. Troubleshooting mechanical problems often involves careful inspection of the deployment mechanism, ensuring that all moving parts are properly aligned and lubricated, and conducting drop tests to verify functionality under realistic conditions. By addressing these common issues proactively, CanSat teams can significantly improve their chances of a successful mission.

Insights and Best Practices for CanSat V3

To ensure the success of a CanSat V3 project, it's essential to incorporate insights and best practices gleaned from previous missions and experiences. This section will explore key strategies that can help teams optimize their designs, streamline their workflows, and achieve their project goals. By adopting these best practices, teams can mitigate risks, enhance performance, and foster a culture of continuous improvement.

Effective project management is fundamental to CanSat success. A well-defined project plan, with clear timelines, milestones, and roles, is crucial for keeping the team on track. Regular team meetings, progress reports, and risk assessments help to identify potential issues early on and ensure that everyone is working towards the same objectives. Utilizing project management tools and techniques can significantly enhance coordination and communication within the team.

Rigorous testing and simulation are also paramount. Before the actual launch, it's important to test every aspect of the CanSat under simulated flight conditions. This includes vibration testing, thermal testing, and altitude simulations. Identifying and addressing potential weaknesses early on can prevent costly failures during the mission. Software simulations can also be used to model the CanSat's behavior and optimize its performance.

Component selection plays a critical role in the success of the project. Choosing the right sensors, microcontrollers, and communication modules can significantly impact the CanSat's capabilities. It's important to consider factors such as accuracy, power consumption, size, and cost when selecting components. Evaluating datasheets, consulting with experts, and conducting thorough research can help teams make informed decisions.

Data analysis and interpretation are key components of the CanSat mission. Collecting data is only the first step; the real value lies in analyzing and interpreting the results. Teams should develop a clear data analysis plan, including the metrics they will track and the tools they will use. Visualizing data through graphs and charts can help to identify trends and patterns. Presenting the findings in a clear and concise manner is essential for communicating the project's outcomes.

Documentation is often overlooked but is crucial for knowledge sharing and future improvements. Maintaining detailed records of the design process, testing procedures, and troubleshooting steps can help the team learn from their experiences and avoid repeating mistakes. Sharing this documentation with the broader CanSat community can contribute to the collective knowledge base and help other teams succeed. Comprehensive documentation also serves as a valuable resource for future CanSat projects.

Finally, collaboration and communication within the team are essential for success. CanSat projects are inherently interdisciplinary, requiring expertise in various fields, such as electronics, software, and mechanics. Fostering a culture of open communication, where team members feel comfortable sharing ideas and concerns, can lead to innovative solutions and better outcomes. Collaboration with mentors, industry experts, and other CanSat teams can provide valuable insights and support. By incorporating these insights and best practices, CanSat V3 teams can enhance their chances of achieving a successful and rewarding mission.

Dashboard Code Explanation

Below is the code for a dashboard designed to display real-time data from a CanSat project. This dashboard utilizes HTML, CSS, and JavaScript, along with the Chart.js library for graphical representation and WebSocket for data communication. The dashboard is structured to provide a comprehensive view of the CanSat's performance, including video feed, sensor readings, and mapping data. Understanding the structure and functionality of this code is crucial for customizing and extending the dashboard to suit specific project needs.

<!DOCTYPE html>
<html lang="fr">
<head>
 <meta charset="UTF-8" />
 <title>Dashboard CanSat – Vidéo + Graphique + Carte</title>
 <meta name="viewport" content="width=device-width, initial-scale=1.0" />

 <!-- Chart.js pour les graphes -->
 <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>

 <style>
 :root {
 --bg: #020617;
 --bg-card: #0f172a;
 --accent: #38bdf8;
 --accent-soft: rgba(56, 189, 248, 0.15);
 --text: #e5e7eb;
 --text-muted: #9ca3af;
 --danger: #f97373;
 --success: #4ade80;
 --warning: #facc15;
 }

 * {
 box-sizing: border-box;
 font-family: system-ui, -apple-system, BlinkMacSystemFont,
 "Segoe UI", sans-serif;
 }

 body {
 margin: 0;
 padding: 16px;
 background: radial-gradient(circle at top, #1e293b 0, #020617 50%);
 color: var(--text);
 }

 .container {
 max-width: 1400px;
 margin: 0 auto;
 }

 header {
 display: flex;
 justify-content: space-between;
 align-items: flex-end;
 margin-bottom: 16px;
 gap: 12px;
 flex-wrap: wrap;
 }

 header h1 {
 margin: 0;
 font-size: 1.6rem;
 }

 header span {
 font-size: 0.9rem;
 color: var(--text-muted);
 }

 .status-badge {
 display: inline-flex;
 align-items: center;
 gap: 6px;
 padding: 4px 10px;
 border-radius: 999px;
 background: var(--accent-soft);
 color: var(--accent);
 font-size: 0.8rem;
 }

 .status-dot {
 width: 8px;
 height: 8px;
 border-radius: 999px;
 background: var(--accent);
 }

 .cards {
 display: grid;
 grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
 gap: 12px;
 margin-bottom: 16px;
 }

 .card {
 background: linear-gradient(
 135deg,
 rgba(15, 23, 42, 0.95),
 rgba(15, 23, 42, 1)
 );
 border-radius: 16px;
 padding: 12px 14px;
 box-shadow: 0 15px 30px rgba(0, 0, 0, 0.3);
 border: 1px solid rgba(148, 163, 184, 0.16);
 position: relative;
 overflow: hidden;
 }

 .card-title {
 font-size: 0.8rem;
 text-transform: uppercase;
 letter-spacing: 0.07em;
 color: var(--text-muted);
 margin-bottom: 4px;
 }

 .card-value {
 font-size: 1.6rem;
 font-weight: 600;
 }

 .card-unit {
 font-size: 0.9rem;
 margin-left: 4px;
 color: var(--text-muted);
 }

 .card-sub {
 font-size: 0.8rem;
 margin-top: 4px;
 color: var(--text-muted);
 }

 .main-layout {
 display: grid;
 grid-template-columns: 1.4fr 1fr;
 gap: 16px;
 }

 @media (max-width: 1050px) {
 .main-layout {
 grid-template-columns: 1fr;
 }
 }

 .panel {
 background: rgba(15, 23, 42, 0.96);
 border-radius: 16px;
 padding: 12px 14px;
 border: 1px solid rgba(148, 163, 184, 0.18);
 box-shadow: 0 10px 25px rgba(0, 0, 0, 0.4);
 }

 .panel h2 {
 font-size: 1rem;
 margin: 0 0 8px 0;
 }

 /* === VIDÉO + INSPECTEUR DE PIXELS === */
 .video-layout {
 display: grid;
 grid-template-columns: auto minmax(220px, 280px);
 gap: 12px;
 align-items: flex-start;
 }

 @media (max-width: 900px) {
 .video-layout {
 grid-template-columns: 1fr;
 }
 }

 .video-container {
 position: relative;
 width: 512px;
 height: 540px;
 max-width: 100%;
 margin: 0 auto;
 }

 #cansat-video {
 width: 100%;
 height: 100%;
 background: #020617;
 border-radius: 12px;
 border: 1px solid rgba(148,163,184,0.3);
 object-fit: cover;
 display: block;
 }

 #video-overlay {
 position: absolute;
 inset: 0;
 border-radius: 12px;
 cursor: crosshair;
 }

 .pixel-info {
 background: #020617;
 border-radius: 12px;
 border: 1px solid rgba(148,163,184,0.3);
 padding: 10px;
 font-size: 0.85rem;
 }

 .pixel-swatch {
 width: 40px;
 height: 40px;
 border-radius: 8px;
 border: 1px solid rgba(148,163,184,0.3);
 margin-right: 8px;
 }

 .pixel-row {
 display: flex;
 align-items: center;
 margin-bottom: 6px;
 }

 /* === GRAPHIQUE COMPACT === */
 .chart-container {
 width: 256px;
 height: 160px;
 margin: 0 auto 8px;
 }

 #tempPressureChart {
 width: 100% !important;
 height: 100% !important;
 }

 /* === CARTE INTERNE 512 x 540, AFFICHÉE EN 256 x 270 === */
 .map-container {
 position: relative;
 width: 256px;
 height: 270px;
 margin: 8px auto 0;
 }

 #map-canvas {
 width: 256px;
 height: 270px;
 border-radius: 12px;
 border: 1px solid rgba(148,163,184,0.3);
 background: radial-gradient(circle at center, #0b1120 0, #020617 70%);
 display: block;
 }

 .map-legend {
 font-size: 0.8rem;
 color: var(--text-muted);
 margin-top: 4px;
 text-align: center;
 }

 #final-map-link {
 display: none;
 font-size: 0.8rem;
 margin-top: 6px;
 text-align: center;
 color: #38bdf8;
 text-decoration: underline;
 cursor: pointer;
 }

 .final-map-container {
 display: none;
 margin-top: 8px;
 text-align: center;
 font-size: 0.8rem;
 color: var(--text-muted);
 }

 .final-map-container img {
 max-width: 256px;
 border-radius: 12px;
 border: 1px solid rgba(148,163,184,0.4);
 display: block;
 margin: 4px auto 0;
 background: #000;
 }

 .footer {
 margin-top: 10px;
 font-size: 0.75rem;
 color: var(--text-muted);
 text-align: right;
 }
 </style>
</head>
<body>
 <div class="container">
 <header>
 <div>
 <h1>CanSat – Vidéo, Mesures & Carte</h1>
 <span>Température, pression, altitude et vidéo synchronisée</span>
 </div>
 <div class="status-badge">
 <span class="status-dot"></span>
 <span id="status-text">Connexion Raspberry Pi…</span>
 </div>
 </header>

 <!-- Cartes valeurs instantanées -->
 <div class="cards">
 <div class="card">
 <div class="card-title">Température</div>
 <div class="card-value">
 <span id="temp-value">--</span><span class="card-unit">°C</span>
 </div>
 <div class="card-sub" id="temp-trend">En attente de données…</div>
 </div>
 <div class="card">
 <div class="card-title">Pression</div>
 <div class="card-value">
 <span id="pressure-value">--</span><span class="card-unit">hPa</span>
 </div>
 <div class="card-sub" id="pressure-trend">En attente de données…</div>
 </div>
 <div class="card">
 <div class="card-title">Altitude calculée</div>
 <div class="card-value">
 <span id="altitude-value">--</span><span class="card-unit">m</span>
 </div>
 <div class="card-sub" id="altitude-comment">En attente de données…</div>
 </div>
 </div>

 <div class="main-layout">
 <!-- Colonne gauche : vidéo + pixels -->
 <div class="panel">
 <h2>Vidéo CanSat & Inspecteur de pixels</h2>
 <div class="video-layout">
 <div class="video-container">
 <video
 id="cansat-video"
 width="512"
 height="540"
 autoplay
 muted
 playsinline
 controls
 ></video>
 <canvas id="video-overlay"></canvas>
 </div>

 <div class="pixel-info">
 <div class="pixel-row">
 <div id="pixel-swatch" class="pixel-swatch"></div>
 <div>
 <div id="pixel-coord">Pixel : (x = --, y = --)</div>
 <div id="pixel-rgba">RGBA : --</div>
 <div id="pixel-hex">HEX : --</div>
 </div>
 </div>
 <div>
 <strong>Dernier timestamp :</strong>
 <span id="pixel-ts">--</span>
 </div>
 <div style="margin-top: 6px; font-size: 0.78rem; color: var(--text-muted);">
 Clique sur n’importe quel point de la vidéo pour obtenir la couleur exacte du pixel.
 </div>
 </div>
 </div>
 </div>

 <!-- Colonne droite : graphique + carte -->
 <div class="panel">
 <h2>Évolution des mesures & Carte des frames</h2>

 <div class="chart-container">
 <canvas id="tempPressureChart"></canvas>
 </div>

 <div class="map-container">
 <!-- Résolution interne 512x540, affichée en 256x270 -->
 <canvas id="map-canvas" width="512" height="540"></canvas>
 </div>
 <div class="map-legend">
 Carte (vue de dessus) construite à partir des frames vidéo.<br />
 Chaque frame est positionnée avec (x, y), angle (yaw) et amplitude (FOV).
 </div>
 <a id="final-map-link" href="#" download="cansat_carte_finale.png">
 Télécharger la carte finale (PNG)
 </a>

 <div id="final-map-container" class="final-map-container">
 Carte finale générée :
 <img id="final-map-img" alt="Carte finale CanSat" />
 </div>
 </div>
 </div>

 <div class="footer">
 Aucune donnée pendant 2 min ⇒ carte figée & exportable.
 </div>
 </div>

 <script>
 // =========================================
 // 1. Paramètres généraux / état
 // =========================================
 const SEA_LEVEL_PRESSURE = 1013.25; // hPa
 const MAX_POINTS = 200;
 const TIMEOUT_MS = 120000; // 2 min

 const tempEl = document.getElementById("temp-value");
 const pressureEl = document.getElementById("pressure-value");
 const altitudeEl = document.getElementById("altitude-value");
 const tempTrendEl = document.getElementById("temp-trend");
 const pressureTrendEl = document.getElementById("pressure-trend");
 const altitudeCommentEl = document.getElementById("altitude-comment");
 const statusTextEl = document.getElementById("status-text");
 const finalMapLinkEl = document.getElementById("final-map-link");
 const finalMapContainerEl = document.getElementById("final-map-container");
 const finalMapImgEl = document.getElementById("final-map-img");

 let lastTelemetryTime = null;
 let missionFinalized = false;
 let ws = null;

 // =========================================
 // 2. Altitude depuis la pression
 // =========================================
 function pressureToAltitude(pressureHpa) {
 const ratio = pressureHpa / SEA_LEVEL_PRESSURE;
 return 44330 * (1 - Math.pow(ratio, 1 / 5.255));
 }

 // =========================================
 // 3. Graphique Temp / Pression / Altitude
 // =========================================
 const chartCtx = document
 .getElementById("tempPressureChart")
 .getContext("2d");

 const chartData = {
 labels: [],
 datasets: [
 {
 label: "Température (°C)",
 data: [],
 yAxisID: "yTemp",
 },
 {
 label: "Pression (hPa)",
 data: [],
 yAxisID: "yPress",
 },
 {
 label: "Altitude (m)",
 data: [],
 yAxisID: "yAlt",
 },
 ],
 };

 const tempPressureChart = new Chart(chartCtx, {
 type: "line",
 data: chartData,
 options: {
 animation: false,
 responsive: true,
 maintainAspectRatio: false,
 scales: {
 x: {
 title: { display: true, text: "Temps" },
 ticks: { maxRotation: 0, autoSkip: true },
 },
 yTemp: {
 position: "left",
 title: { display: true, text: "Température (°C)" },
 },
 yPress: {
 position: "right",
 title: { display: true, text: "Pression (hPa)" },
 grid: { drawOnChartArea: false },
 },
 yAlt: {
 position: "right",
 title: { display: true, text: "Altitude (m)" },
 grid: { drawOnChartArea: false },
 },
 },
 plugins: {
 legend: { labels: { color: "#e5e7eb" } },
 },
 },
 });

 function trendOf(array, lastN = 8) {
 if (array.length < 2) return "stable";
 const slice = array.slice(-lastN);
 const first = slice[0];
 const last = slice[slice.length - 1];
 const diff = last - first;
 const threshold = Math.abs(first) * 0.01 + 0.2;
 if (diff > threshold) return "up";
 if (diff < -threshold) return "down";
 return "stable";
 }

 function updateTrends() {
 const temps = chartData.datasets[0].data;
 const pressures = chartData.datasets[1].data;
 const alts = chartData.datasets[2].data;
 if (temps.length < 2 || pressures.length < 2 || alts.length < 2) return;

 const lastTemp = temps[temps.length - 1];
 const lastPress = pressures[pressures.length - 1];
 const lastAlt = alts[alts.length - 1];

 const tempTrend = trendOf(temps);
 const pressTrend = trendOf(pressures);

 if (tempTrend === "up") {
 tempTrendEl.textContent = "Température en hausse.";
 } else if (tempTrend === "down") {
 tempTrendEl.textContent = "Température en baisse.";
 } else {
 tempTrendEl.textContent = "Température stable.";
 }

 if (pressTrend === "up") {
 pressureTrendEl.textContent =
 "Pression en hausse (altitude en baisse).";
 } else if (pressTrend === "down") {
 pressureTrendEl.textContent =
 "Pression en baisse (altitude en hausse).";
 } else {
 pressureTrendEl.textContent = "Pression stable.";
 }

 let altComment = "";
 if (lastAlt < 100) altComment = "Proche du sol.";
 else if (lastAlt < 1000) altComment = "Altitude moyenne.";
 else altComment = "Altitude élevée.";
 altitudeCommentEl.textContent = altComment;
 }

 function addDataPoint(tempC, pressureHpa) {
 const now = new Date();
 const timeLabel = now.toLocaleTimeString("fr-FR", { hour12: false });
 const altitude = pressureToAltitude(pressureHpa);

 tempEl.textContent = tempC.toFixed(1);
 pressureEl.textContent = pressureHpa.toFixed(1);
 altitudeEl.textContent = altitude.toFixed(1);

 chartData.labels.push(timeLabel);
 chartData.datasets[0].data.push(tempC);
 chartData.datasets[1].data.push(pressureHpa);
 chartData.datasets[2].data.push(altitude);

 if (chartData.labels.length > MAX_POINTS) {
 chartData.labels.shift();
 chartData.datasets.forEach((ds) => ds.data.shift());
 }

 tempPressureChart.update();
 updateTrends();
 }

 // =========================================
 // 4. Vidéo + inspecteur de pixels
 // =========================================
 const videoEl = document.getElementById("cansat-video");
 const overlayCanvas = document.getElementById("video-overlay");
 const overlayCtx = overlayCanvas.getContext("2d");
 const pixelSwatch = document.getElementById("pixel-swatch");
 const pixelCoordEl = document.getElementById("pixel-coord");
 const pixelRgbaEl = document.getElementById("pixel-rgba");
 const pixelHexEl = document.getElementById("pixel-hex");
 const pixelTsEl = document.getElementById("pixel-ts");

 const frameCanvas = document.createElement("canvas");
 const frameCtx = frameCanvas.getContext("2d");

 function resizeOverlay() {
 const rect = videoEl.getBoundingClientRect();
 overlayCanvas.width = rect.width;
 overlayCanvas.height = rect.height;
 }

 videoEl.addEventListener("loadedmetadata", resizeOverlay);
 window.addEventListener("resize", resizeOverlay);

 overlayCanvas.addEventListener("click", (evt) => {
 if (!videoEl.videoWidth || !videoEl.videoHeight) return;

 const rect = overlayCanvas.getBoundingClientRect();
 const scaleX = videoEl.videoWidth / rect.width;
 const scaleY = videoEl.videoHeight / rect.height;

 const x = Math.floor((evt.clientX - rect.left) * scaleX);
 const y = Math.floor((evt.clientY - rect.top) * scaleY);

 frameCanvas.width = videoEl.videoWidth;
 frameCanvas.height = videoEl.videoHeight;
 frameCtx.drawImage(videoEl, 0, 0, frameCanvas.width, frameCanvas.height);

 const pixel = frameCtx.getImageData(x, y, 1, 1).data;
 const [r, g, b, a] = pixel;
 const hex =
 "#" +
 [r, g, b]
 .map((v) => v.toString(16).padStart(2, "0"))
 .join("")
 .toUpperCase();

 pixelSwatch.style.backgroundColor = hex;
 pixelCoordEl.textContent = `Pixel : (x = ${x}, y = ${y})`;
 pixelRgbaEl.textContent = `RGBA : (${r}, ${g}, ${b}, ${a})`;
 pixelHexEl.textContent = `HEX : ${hex}`;
 pixelTsEl.textContent = new Date().toLocaleTimeString("fr-FR", {
 hour12: false,
 });
 });

 // =========================================
 // 5. Carte des frames (interne 512x540)
 // =========================================
 const mapCanvas = document.getElementById("map-canvas");
 const mapCtx = mapCanvas.getContext("2d");
 const MAP_WIDTH = 512;
 const MAP_HEIGHT = 540;
 const MAP_SCALE = 0.5; // mètres -> pixels (à ajuster)

 mapCanvas.width = MAP_WIDTH;
 mapCanvas.height = MAP_HEIGHT;

 const frames = []; // {imageCanvas, pose:{x,y,z,yaw,fov,...}, timestamp}

 function degToRad(d) {
 return (d * Math.PI) / 180;
 }

 function captureFrameWithPose(pose) {
 if (missionFinalized) return;
 if (!videoEl.videoWidth || !videoEl.videoHeight) return;

 const miniW = 80;
 const miniH = 60;

 const miniCanvas = document.createElement("canvas");
 miniCanvas.width = miniW;
 miniCanvas.height = miniH;
 const miniCtx = miniCanvas.getContext("2d");
 miniCtx.drawImage(videoEl, 0, 0, miniW, miniH);

 frames.push({
 imageCanvas: miniCanvas,
 pose: pose,
 timestamp: new Date(),
 });

 if (frames.length > 200) frames.shift();

 redrawMap();
 }

 function redrawMap() {
 const w = MAP_WIDTH;
 const h = MAP_HEIGHT;

 mapCtx.clearRect(0, 0, w, h);

 const grd = mapCtx.createRadialGradient(
 w / 2,
 h / 2,
 10,
 w / 2,
 h / 2,
 Math.max(w, h) / 1.3
 );
 grd.addColorStop(0, "#020617");
 grd.addColorStop(1, "#000000");
 mapCtx.fillStyle = grd;
 mapCtx.fillRect(0, 0, w, h);

 // Axes
 mapCtx.strokeStyle = "rgba(148,163,184,0.3)";
 mapCtx.lineWidth = 1;
 mapCtx.beginPath();
 mapCtx.moveTo(w / 2, 0);
 mapCtx.lineTo(w / 2, h);
 mapCtx.moveTo(0, h / 2);
 mapCtx.lineTo(w, h / 2);
 mapCtx.stroke();

 frames.forEach((f) => {
 const { x, y, z, yaw, fov } = f.pose;
 const cx = w / 2 + x * MAP_SCALE;
 const cy = h / 2 - y * MAP_SCALE;
 const angle = degToRad(yaw || 0);

 const imgW = f.imageCanvas.width;
 const imgH = f.imageCanvas.height;

 mapCtx.save();
 mapCtx.translate(cx, cy);
 mapCtx.rotate(angle);

 mapCtx.strokeStyle = "rgba(56,189,248,0.6)";
 mapCtx.lineWidth = 1;
 mapCtx.strokeRect(-imgW / 2 - 2, -imgH / 2 - 2, imgW + 4, imgH + 4);

 mapCtx.drawImage(f.imageCanvas, -imgW / 2, -imgH / 2);

 if (fov) {
 const len = 40;
 const halfFovRad = degToRad(fov / 2);
 mapCtx.beginPath();
 mapCtx.moveTo(0, 0);
 mapCtx.lineTo(len, -Math.tan(halfFovRad) * len);
 mapCtx.moveTo(0, 0);
 mapCtx.lineTo(len, Math.tan(halfFovRad) * len);
 mapCtx.strokeStyle = "rgba(250,204,21,0.7)";
 mapCtx.stroke();
 }

 mapCtx.restore();
 });

 mapCtx.fillStyle = "#e5e7eb";
 mapCtx.font = "10px system-ui";
 mapCtx.fillText("Origine (0,0)", w / 2 + 6, h / 2 - 6);
 }

 // =========================================
 // 6. Télémétrie – entrée principale
 // =========================================
 /**
 * Format JSON attendu :
 * {
 * "temp": 21.3,
 * "pressure": 1008.5,
 * "x": 10.0,
 * "y": -5.0,
 * "z": 150.0,
 * "yaw": 45.0,
 * "pitch": 0.0,
 * "roll": 0.0,
 * "fov": 60.0
 * }
 */
 function onTelemetry(data) {
 lastTelemetryTime = Date.now();
 if (missionFinalized) return;

 if (typeof data.temp === "number" && typeof data.pressure === "number") {
 addDataPoint(data.temp, data.pressure);
 }

 captureFrameWithPose({
 x: data.x || 0,
 y: data.y || 0,
 z: data.z || 0,
 yaw: data.yaw || 0,
 pitch: data.pitch || 0,
 roll: data.roll || 0,
 fov: data.fov || 0,
 });
 }

 // =========================================
 // 7. WebSocket avec le Raspberry Pi
 // =========================================
 // ⚠️ Remplace par l’IP de ton Raspberry Pi
 const RASPI_HOST = "192.168.0.42";
 const wsUrl = `ws://${RASPI_HOST}:8765`;

 function connectWebSocket() {
 if (missionFinalized) return;

 ws = new WebSocket(wsUrl);

 ws.onopen = () => {
 statusTextEl.textContent = "Connecté au Raspberry Pi (" + wsUrl + ")";
 };

 ws.onmessage = (event) => {
 try {
 const data = JSON.parse(event.data);
 onTelemetry(data);
 } catch (e) {
 console.error("Erreur JSON télémétrie:", e);
 }
 };

 ws.onerror = () => {
 if (!missionFinalized) {
 statusTextEl.textContent =
 "Erreur WebSocket – vérifie le script sur le Raspberry Pi.";
 }
 };

 ws.onclose = () => {
 if (missionFinalized) {
 statusTextEl.textContent = "Session terminée – connexion fermée.";
 } else {
 statusTextEl.textContent = "Déconnecté – tentative de reconnexion…";
 setTimeout(connectWebSocket, 3000);
 }
 };
 }

 connectWebSocket();

 // =========================================
 // 8. Source vidéo (à adapter à ton flux)
 // =========================================
 // Exemple si tu as un flux HTTP sur le Pi :
 // videoEl.src = "http://" + RASPI_HOST + ":8080/stream";
 //
 // Pour tester localement :
 // videoEl.src = "test.mp4";

 // =========================================
 // 9. Timeout 2 minutes -> carte finale
 // =========================================
 function finalizeMission() {
 if (missionFinalized) return;
 missionFinalized = true;

 statusTextEl.textContent =
 "Aucune donnée depuis 2 minutes : carte finale générée.";

 try {
 const dataURL = mapCanvas.toDataURL("image/png");
 // Lien de téléchargement
 finalMapLinkEl.href = dataURL;
 finalMapLinkEl.style.display = "block";
 // Prévisualisation dans la page
 finalMapImgEl.src = dataURL;
 finalMapContainerEl.style.display = "block";
 } catch (e) {
 console.error("Erreur génération PNG carte finale:", e);
 }

 if (ws && ws.readyState === WebSocket.OPEN) {
 ws.close();
 }
 }

 setInterval(() => {
 if (missionFinalized) return;
 if (!lastTelemetryTime) return;
 const elapsed = Date.now() - lastTelemetryTime;
 if (elapsed > TIMEOUT_MS) {
 finalizeMission();
 }
 }, 10000);
 </script>
</body>
</html>

HTML Structure

The HTML structure is divided into several key sections:

  1. Head Section: Includes metadata, title, viewport settings, and links to external resources such as Chart.js and custom CSS styles.
  2. Header: Displays the main title, a subtitle, and a status badge indicating the connection status with the Raspberry Pi.
  3. Instant Value Cards: Shows real-time sensor data, including temperature, pressure, and calculated altitude. These cards provide a quick overview of the CanSat’s current state.
  4. Main Layout: Divides the dashboard into two main columns. The left column contains the video feed and pixel inspector, while the right column displays the sensor data graph and the map of video frames.
  5. Video Feed and Pixel Inspector: The left column features a video element displaying the CanSat's video stream and a canvas overlay for pixel inspection.
  6. Sensor Data Graph and Map: The right column includes a Chart.js graph showing temperature, pressure, and altitude trends over time, as well as a canvas for mapping video frames.
  7. Footer: Provides additional information, such as the timeout status for generating the final map.

CSS Styling

The CSS styles are defined within the <style> tag in the <head> section. Key aspects of the CSS include:

  • Custom Properties: Uses :root to define custom CSS properties for colors, making it easier to maintain a consistent theme.
  • Layout: Employs flexbox and grid layouts to structure the dashboard components. The main-layout class uses grid-template-columns to create a responsive two-column layout.
  • Card Styling: The .card class provides a visually appealing design for the instant value cards with gradients, shadows, and borders.
  • Responsive Design: Media queries are used to adjust the layout for different screen sizes, ensuring the dashboard remains functional and visually appealing on various devices.

JavaScript Functionality

The JavaScript code handles data fetching, chart updates, video processing, and map generation. Key functions and components include:

  • WebSocket Connection: Establishes a WebSocket connection with the Raspberry Pi to receive real-time telemetry data. The connectWebSocket function manages the connection, reconnection attempts, and data handling.
  • Data Handling: The onTelemetry function parses incoming JSON data, updates the instant value cards, adds data points to the chart, and captures video frames for mapping.
  • Chart.js Integration: Uses Chart.js to create a line graph displaying temperature, pressure, and altitude over time. The chart is configured with multiple y-axes for each data type.
  • Video and Pixel Inspection: Manages the video feed, sets up a canvas overlay for pixel inspection, and handles click events to display pixel information.
  • Mapping: Captures video frames and their corresponding pose data to generate a map of the CanSat's trajectory. The map is drawn on a canvas element, with each frame represented as a small image.
  • Altitude Calculation: The pressureToAltitude function converts pressure readings to altitude using a standard atmospheric pressure formula.
  • Timeout and Final Map Generation: Implements a timeout mechanism that triggers the generation of a final map if no data is received for a specified period. The finalizeMission function generates a PNG image of the map and provides a download link.

This dashboard code provides a robust foundation for monitoring CanSat V3 missions. By understanding the HTML structure, CSS styling, and JavaScript functionality, users can customize and extend the dashboard to meet their specific requirements. The integration of real-time data, video feed, and mapping capabilities offers a comprehensive view of the CanSat's performance, making it an invaluable tool for mission analysis and troubleshooting.

Conclusion

In conclusion, discussions surrounding CanSat V3 encompass a wide range of topics, from troubleshooting common issues to sharing best practices and design insights. This article has aimed to provide a comprehensive overview of these discussions, offering valuable solutions and strategies for participants. By addressing the challenges proactively and leveraging the collective knowledge of the CanSat community, teams can significantly enhance their chances of success.

We explored the critical aspects of troubleshooting, including power management, sensor performance, communication systems, software glitches, and mechanical failures. Each of these areas presents unique challenges, but with systematic problem-solving and rigorous testing, teams can identify and mitigate potential issues. Understanding these common pitfalls is the first step towards building a robust and reliable CanSat system. Furthermore, we delved into insights and best practices that can streamline the project lifecycle. Effective project management, rigorous testing and simulation, component selection, data analysis, documentation, and collaboration are key elements for a successful mission. By adopting these strategies, teams can optimize their workflows, improve performance, and foster a culture of continuous improvement.

Additionally, we examined the dashboard code, which serves as a vital tool for real-time monitoring and analysis during CanSat missions. The dashboard integrates video feeds, sensor data, and mapping capabilities, providing a comprehensive view of the CanSat's performance. Understanding the HTML structure, CSS styling, and JavaScript functionality of this dashboard enables users to customize and extend it to meet their specific needs.

The CanSat V3 project represents a unique opportunity for students and enthusiasts to engage in hands-on learning and innovation in the field of space technology. By embracing the challenges and sharing knowledge within the community, participants can develop valuable skills and contribute to the advancement of aerospace engineering. The discussions and insights shared in this article are intended to serve as a resource for current and future CanSat teams, fostering a collaborative environment where everyone can learn and grow.

In the spirit of continuous learning and exploration, we encourage readers to further their knowledge by exploring reputable external resources. For more detailed information on CanSat projects and related topics, we recommend visiting the European Space Agency (ESA) Education website. This resource offers a wealth of information, including project guidelines, educational materials, and updates on space-related initiatives. By staying informed and engaged, the CanSat community can continue to push the boundaries of what's possible, inspiring future generations of engineers and scientists.