Blueprint

CO2 Monitor

This project uses an Arduino Uno R4 WiFi and a K30 CO₂ sensor powered by a 5–9W solar panel to monitor live CO₂ levels. Data is uploaded to a custom HTML webpage, enabling real-time environmental tracking. The system is designed to be scalable, with each unit costing around $200 with solar power and $130 for the plug in version you can see cleaner code and other cool stuff at this link https://blueprint.hackclub.com/projects/834

Created by Ethan Ethan

No Grant

16 views

0 followers

Timeline

Ethan Ethan added to the journal ago

3D Printed case

It looks okay. I'm going to try to make it better and easier to print without supports. Let me know if you have any ideas. I'm also going to make multiple versions to suit all of the types of power sources.

Screenshot 2025-10-27 7.53.12 AM

Ethan Ethan added to the journal ago

Web App Updates

I've updated the web app with a new color scheme, a more accurate graph, and a map; however, I'm still experiencing issues with the CO2 sensor reading -1 ppm.

Screenshot 2025-10-27 7.49.39 AM

CAN CAN ⚡🚀 approved CO2 Monitor ago

Tickets awarded: 400 tickets

Ethan Ethan submitted CO2 Monitor for review ago

Ethan Ethan added to the journal ago

Bug Fixing

Fixed a bug involving the rendering of the graph in some desktop formats.

Fixed a bug involving the incorrect reading of CO2 after the website was reloaded repeatedly

Fixed a bug where older versions of Chrome would repeatedly crash after opening the page and selecting an alternate time frame.

Screenshot 2025-10-23 6.36.15 PM.png

Ethan Ethan added to the journal ago

Fixed Dashbord?

After resetting the device and checking the error codes I did not see anything that would have caused this, I reset the device and its working normally now, I'm going to remove the bad data from the database so future calculations and things are not affected, also working on fixing a bug where a mobile display of the web app used mobile graph sizes and an other bug where the colored dot is not correctly displayed on the map when viewed in fullscreen.

Screenshot 2025-10-22 10.21.12 AM.png

Ethan Ethan added to the journal ago

Assembling the Enclosure

A few YouTube videos later and I've made the top of the box slide in the hole it belongs in, only issue is how to print it with the least amount of supports

I may also flip the whole thing to make it more waterproof having the door/access panel on the bottom so the electronics cant get flooded.

image.png

Ethan Ethan added to the journal ago

That Cant Be Good

I woke up this morning to find that my updated code from the previous night had caused an error where the HTML web page was reading over 50,000 ppm "that cant be right" is thought to myself. Anyway I reset the device and that seemed to fix it will check the error log and firebase data

image.png

Ethan Ethan added to the journal ago

3D printed enclosure

began working on the enclosure to house the electronics, the plan is to print these with pla and spay paint them with a clear coat to make them water tight

Screenshot 2025-10-21 12.41.31 PM.png

trying to figure out the best way to do this having no experience with onshape but determined to learn cad

Ethan Ethan added to the journal ago

UPDATED bom.csv

I updated the bom.csv to look nice and have tax, I also live in Oregon (where there is no sales tax) so I included an option that does not include tax, the updated values and calculations are in the latest GitHub repository linked at the header of this page.

Screenshot 2025-10-21 8.47.39 AM.png

Ethan Ethan added to the journal ago

VIEW MY PROJECT

IM LIVE

my sensor shuld be live right now check this link

THIS LINK

if you dont see anything than my arduino is probably going through a midlife crisis however if you select the all time option you can see past datapoints

Screenshot 2025-10-20 at 8.52.10 PM.png

finally got this live after firebase decided my security was not up to par, oh well

Ethan Ethan added to the journal ago

Iphone web app on home screen tutorial

To add the web app to your iphones home screen:

at the bottom of the page in sefari click the 3 dots
click share
scroll down to add to home screen
the app icon shuld apper along with the default name
click done
view on your home screen

Screenshot 2025-10-20 at 8.46.57 PM.png
Screenshot 2025-10-20 at 8.46.19 PM.png

Ethan Ethan added to the journal ago

Bug fix

fixed a bug whare when the web app was deployed from safari on iphone the map would not load correctly has ben updated in the latest GitHub repository

Screenshot 2025-10-20 at 8.41.42 PM.png

Ethan Ethan added to the journal ago

BETER CODE IS HERE

there's more better code if you have some of the newer Arduino boards or plan to use an esp instead this code should work.
and of course I forgot to remove my API key so had to change that opsie
every line of this code has been tested and works but there's more on the horizon

download.jpg

took me a minute to get this done tho

Ethan Ethan added to the journal ago

MORE BUGS :(

Getting Firebase and GitHub Pages to work together for real-time CO₂ updates was easily one of the most challenging parts yet. I ran into a ton of bugs along the way — everything from failed database writes to JavaScript fetch errors. Early on, Firebase kept throwing permission-denied errors because my database rules weren’t configured correctly, which prevented the Arduino from posting data. I also had to debug SSL and certificate issues when sending HTTPS requests from the Arduino Uno R4 WiFi — the board wouldn’t connect until I updated the root certificate and double-checked the endpoint URL format. On the front-end side, my fetch requests initially wouldn’t display live data due to CORS restrictions and incorrect JSON parsing. Once that was fixed, I ran into chart refresh bugs, where Chart.js would duplicate datasets or fail to clear old data when new readings came in. On top of that, GitHub Pages introduced its own quirks — it aggressively cached my JavaScript files, so changes wouldn’t appear even after new commits. I fixed that by adding version query strings to my script imports (script.js?v=2.1) and clearing GitHub’s cache manually through commit pushes. Finally, I spent hours tracking down responsive layout issues on mobile browsers — text would overlap, buttons would clip, and graphs would extend off-screen until I added flexible containers, percentage-based widths, and viewport scaling. After countless rounds of testing and debugging, the system now runs smoothly, fetching real-time CO₂ readings from Firebase and displaying them beautifully on any device.

image.png

spent most of the weekend on this :( dang bugs
on the plus side I won my ultimate turniment

Ethan Ethan added to the journal ago

LIVE Web App with firebase and github

One of the most exciting parts of this project was realizing that I could use Firebase and GitHub Pages together to make my CO₂ monitor work like a real web app that runs on iPhone and Android. By sending the live sensor data from the Arduino Uno R4 WiFi directly to Firebase Realtime Database, I was able to stream updates instantly to any device — no refreshing needed. Then, by hosting my HTML, CSS, and JavaScript files on GitHub Pages, I turned the interface into a fully functional, mobile-friendly dashboard that looks and feels like a native app. The best part is that GitHub lets me update the code instantly — if I fix a bug or tweak the layout, I just push a new commit and the live site updates automatically. Seeing the live CO₂ data graph update on my phone and tablet in real time felt like a huge win — it’s proof that the whole system works seamlessly across platforms. It’s fast, reliable, and 100% free to host, which makes it not just a prototype, but a complete, scalable web solution.
Screenshot 2025-10-20 1.22.48 PM.png

Ethan Ethan added to the journal ago

MORE BUGS :(

Setting up Firebase and GitHub Pages to work together took a lot of trial and error. I started by creating a Firebase project and linking it to my Arduino using the Realtime Database REST API so it could send JSON-formatted CO₂ readings over HTTPS. One of the first major bugs I ran into was authentication errors — Firebase kept rejecting my POST requests until I adjusted the database rules to allow authenticated writes and formatted the URL correctly with the “.json” endpoint. Another issue came from HTTPS certificate mismatches when connecting from the Arduino Uno R4 WiFi, which required using the WiFiClientSecure library and updating the certificate fingerprint manually. On the front-end, I ran into CORS policy errors when fetching live data from Firebase in JavaScript, which I fixed by restructuring my fetch calls and enabling the proper Firebase web API configuration. Hosting on GitHub Pages introduced its own challenges: cached data wasn’t refreshing properly, so I had to add cache-busting query parameters to the Firebase fetch URLs and disable aggressive caching in the meta tags. I also fought through layout bugs when embedding the site in Google Sites and mobile browsers — charts would resize incorrectly or text would clip on smaller screens until I implemented CSS media queries, viewport scaling, and dynamic resizing with JavaScript. After several hours of debugging and cross-platform testing, the site finally rendered smoothly and updated in real time on all devices.

Screenshot 2025-10-20 7.49.24 AM.png

Ethan Ethan added to the journal ago

web.app

To take the project beyond a local network webpage, I integrated Firebase and GitHub Pages to turn it into a true web app that could be accessed from anywhere. Using Firebase Realtime Database, I set up cloud data logging so that every CO₂ reading sent from the Arduino Uno R4 WiFi was stored with timestamps and could be fetched dynamically by the web interface. I then hosted the front-end files on GitHub Pages, allowing the same live data to be displayed from a clean, fast, and free hosting solution. A key part of this process was ensuring that the interface looked and worked great on all devices — I tested and optimized it for desktop, tablet, and mobile using responsive design principles and Firebase’s real-time updates to sync data instantly. This combination of cloud hosting, database integration, and open-source deployment made the system not only visually consistent but also scalable, so future sensors or dashboards can be added without changing the underlying infrastructure.

Screenshot 2025-10-20 7.47.47 AM.png

Screenshot 2025-10-20 7.47.23 AM.png

Ethan Ethan added to the journal ago

Bug Squashing and Device Formatting

One of the most time-consuming yet rewarding parts of this project was refining the HTML interface for consistent display across all devices. I spent around five and a half hours debugging layout issues to make sure the live CO₂ readings, graph, and status indicators looked clean and readable on desktop, tablet, and mobile screens alike. This involved experimenting with responsive CSS, adjusting flexbox and grid layouts, fine-tuning font scaling, and testing in multiple browsers. At first, certain elements like the live graph or CO₂ value display would overlap or resize incorrectly on smaller screens, but after several iterations and media query adjustments, I achieved a responsive design that adapts smoothly to any format. The end result is a simple, functional, and professional-looking dashboard that can be viewed seamlessly from any device.

Screenshot 2025-10-20 7.44.29 AM.png

Ethan Ethan added to the journal ago

Next Steps

image.png

'














































































Task Difficulty Estimated Time Benefit
Add real-time alerts (color changes or warnings when CO₂ exceeds thresholds) Medium 2–4 hours Improves immediate safety awareness and visual feedback
Display longer-term trends (store more readings in Firebase and plot on a scrollable chart) Medium 3–5 hours Provides historical context for CO₂ levels
Add averages, min/max, rate-of-change indicators Medium 2–3 hours Gives more insight into air quality patterns
Implement error handling & auto-reconnect (Wi-Fi, sensor, Firebase failures) High 4–6 hours Increases system reliability and reduces downtime
Optimize solar/power management (sleep modes, low-power updates) Medium 3–5 hours Extends off-grid operation and efficiency
Make webpage mobile-friendly (responsive design for phones/tablets) Low 2–3 hours Improves accessibility and user experience
Set up multi-sensor support (monitor multiple locations) High 6–8 hours Expands project capability for spatial air quality monitoring
Add email/SMS alerts for high CO₂ levels Medium 3–5 hours Provides automated notifications for critical conditions
Integrate additional sensors (temperature, humidity, VOC) Medium 4–6 hours Creates a more comprehensive air quality monitoring system
Polish documentation (wiring diagrams, step-by-step guides, troubleshooting) Low 2–4 hours Makes it easier for others to replicate and learn from the project
Public sharing / deployment (GitHub, personal portfolio, custom domain) Low 2–3 hours Showcases the project and builds portfolio/credibility
'

made it in HTML cuz I was bord in class

Ethan Ethan added to the journal ago

NEXT STEPS

Screenshot 2025-10-17 4.00.12 PM.png

<table border="1" cellpadding="8" cellspacing="0" style="border-collapse: collapse; width: 100%; font-family: Arial, sans-serif;">
<thead style="background-color: #007BFF; color: white;">
<tr>
<th>Task</th>
<th>Difficulty</th>
<th>Estimated Time</th>
<th>Benefit</th>
</tr>
</thead>
<tbody>
<tr>
<td>Add real-time alerts (color changes or warnings when CO₂ exceeds thresholds)</td>
<td>Medium</td>
<td>2–4 hours</td>
<td>Improves immediate safety awareness and visual feedback</td>
</tr>
<tr>
<td>Display longer-term trends (store more readings in Firebase and plot on a scrollable chart)</td>
<td>Medium</td>
<td>3–5 hours</td>
<td>Provides historical context for CO₂ levels</td>
</tr>
<tr>
<td>Add averages, min/max, rate-of-change indicators</td>
<td>Medium</td>
<td>2–3 hours</td>
<td>Gives more insight into air quality patterns</td>
</tr>
<tr>
<td>Implement error handling & auto-reconnect (Wi-Fi, sensor, Firebase failures)</td>
<td>High</td>
<td>4–6 hours</td>
<td>Increases system reliability and reduces downtime</td>
</tr>
<tr>
<td>Optimize solar/power management (sleep modes, low-power updates)</td>
<td>Medium</td>
<td>3–5 hours</td>
<td>Extends off-grid operation and efficiency</td>
</tr>
<tr>
<td>Make webpage mobile-friendly (responsive design for phones/tablets)</td>
<td>Low</td>
<td>2–3 hours</td>
<td>Improves accessibility and user experience</td>
</tr>
<tr>
<td>Set up multi-sensor support (monitor multiple locations)</td>
<td>High</td>
<td>6–8 hours</td>
<td>Expands project capability for spatial air quality monitoring</td>
</tr>
<tr>
<td>Add email/SMS alerts for high CO₂ levels</td>
<td>Medium</td>
<td>3–5 hours</td>
<td>Provides automated notifications for critical conditions</td>
</tr>
<tr>
<td>Integrate additional sensors (temperature, humidity, VOC)</td>
<td>Medium</td>
<td>4–6 hours</td>
<td>Creates a more comprehensive air quality monitoring system</td>
</tr>
<tr>
<td>Polish documentation (wiring diagrams, step-by-step guides, troubleshooting)</td>
<td>Low</td>
<td>2–4 hours</td>
<td>Makes it easier for others to replicate and learn from the project</td>
</tr>
<tr>
<td>Public sharing / deployment (GitHub, personal portfolio, custom domain)</td>
<td>Low</td>
<td>2–3 hours</td>
<td>Showcases the project and builds portfolio/credibility</td>
</tr>
</tbody>
</table>

made it in HTML cuz I was bored in class

Ethan Ethan added to the journal ago

RESERCHING

The first step in this project was researching the best way to measure CO₂ levels reliably and accurately. I spent a lot of time looking into different sensors, comparing ranges, accuracies, and communication protocols. After evaluating several options, I settled on the K30 CO₂ sensor because of its high measurement range up to 10,000 ppm and the availability of UART communication, which made it compatible with many microcontrollers. I also had to consider factors like power consumption and ease of integration, as I wanted the system to eventually run continuously on a small solar setup. This research phase was critical in defining the core components and capabilities of the project.

Next, I focused on choosing the right microcontroller board. Initially, I went with the ESP8266 due to its built-in Wi-Fi and low cost, thinking it would simplify the wireless data collection. However, as I started testing, I realized the ESP8266’s limited RAM and I/O flexibility could become a bottleneck when working with the K30 and handling additional features like cloud integration and real-time display. After weighing the pros and cons, I switched to the Arduino Uno R4 WiFi, which provided better reliability, more memory, and a familiar development environment while still including Wi-Fi capabilities. This decision required adjusting my wiring, code, and setup but ultimately made the system more stable.

Once the board was selected, I explored the Arduino Cloud IoT system and the Arduino Web Editor, thinking it would be a straightforward way to push data online. I spent time learning how to set up cloud dashboards, manage devices, and use Arduino’s built-in libraries to send CO₂ readings to the cloud. While the Arduino Cloud worked well for basic testing, I realized it was somewhat restrictive for a fully customized project and would increase long-term costs for public accessibility. After experimenting with it, I decided not to use Arduino Cloud for this project and instead chose Firebase as the data storage solution, which offered free, real-time, globally accessible data with much more flexibility.

The research and experimentation phase taught me a lot about microcontroller capabilities, cloud systems, and the trade-offs between cost, ease-of-use, and functionality. I learned not only about CO₂ sensors and wireless boards but also about choosing the right software tools for IoT projects, managing data streams, and understanding the limits of different platforms. Each decision—from selecting the sensor, testing multiple boards, and exploring cloud platforms—was informed by careful research and hands-on experimentation. This iterative process ensured that by the time I began coding the live web interface and embedding it on Google Sites, I had a solid foundation of knowledge to build a robust and accessible CO₂ monitoring system.

Screenshot 2025-10-17 3.33.36 PM.png

Ethan Ethan added to the journal ago

HTML Embed In Google Sites

This part of the project focuses on creating a dynamic, web-based CO₂ monitor that displays live readings from the K30 sensor using Firebase as the backend. The Arduino continuously pushes CO₂ measurements, along with timestamps, to a Firebase Realtime Database, which allows for real-time updates anywhere with an internet connection. On the frontend, a custom HTML page embedded in Google Sites retrieves the latest data and displays it in two ways: a large numeric display showing the current CO₂ level and a live-updating line chart visualizing recent trends over time using Chart.js. This setup not only makes the data accessible globally without requiring local Wi-Fi but also provides a clear, interactive, and visually appealing way to monitor CO₂ levels in real time, all while keeping costs low through free tools.

Annotation 2025-10-16 142830.png

Ethan Ethan added to the journal ago

Push The Data To Firebase and Next Steps

The next step in the project is to make the CO₂ readings from the K30 sensor on the Arduino Uno R4 WiFi accessible from anywhere by connecting the system to Firebase. We plan to have the Arduino push live CO₂ data to a Firebase Realtime Database or Firestore over Wi-Fi. This setup allows us to store readings securely in the cloud, so they are accessible from any device with an internet connection. Using Firebase also gives us built-in tools for authentication, security rules, and real-time updates, which makes it easier to manage and protect the data while keeping the system scalable.

Once the data is live in Firebase, we will create a custom HTML page that pulls the CO₂ readings and updates dynamically using Firebase’s JavaScript SDK. To keep costs low and simplify hosting, we’ll embed this page into Google Sites, which provides a free, easy-to-use platform for sharing our live data. The HTML page can display the CO₂ levels in real-time using charts or numeric displays, and it will update automatically without needing a page refresh. This approach allows anyone with the link to view the readings from any device, effectively turning our local CO₂ monitor into a globally accessible, low-cost monitoring system.

CO2_Monitor_Diagram.png

Ethan Ethan added to the journal ago

Live Data To Firebase

In this project, the Arduino Uno R4 WiFi reads real-time CO₂ levels from the K30 sensor via UART and initially displayed the data on a live-updating webpage hosted directly from the Arduino. The webpage used Chart.js to plot CO₂ readings over time, allowing wireless monitoring from any device on the same network. While this method was convenient for local visualization, it had limitations: data was only available while connected to the Arduino’s network, and historical readings could not be easily stored or analyzed. To improve this, the project was enhanced by sending CO₂ readings, along with timestamps, to a Firebase Realtime Database using HTTPS. This approach provides cloud-based logging, enabling secure remote access to historical data, long-term storage, and potential integration with analytics or dashboards. By combining live visualization with cloud logging, the system now offers both instant monitoring and persistent, accessible data for analysis over time.

https://firebase.studio/

Screenshot 2025-10-17 3.04.04 PM.png

Ethan Ethan added to the journal ago

Web Server with Arduino UNO R4 Wifi

I used the Arduino Uno R4 WiFi’s built-in Wi-Fi capabilities to create a local web server that displayed live CO₂ data. After connecting the Arduino to a Wi-Fi network, I programmed it to serve an HTML webpage accessible via its IP address. The webpage dynamically updated with the latest CO₂ readings received from the K30 sensor, allowing real-time monitoring through any browser on the same network. This made the system fully wireless—no USB connection was required to view the data. However, the limitation was that the webpage could only be accessed by devices connected to the same Wi-Fi network as the Arduino and the Arduino required a constant supply of power from a power bank.

EXAMPLE HTML AND PHOTO ONLY. DOES NOT DIPLAY REAL DATA

Screenshot 2025-10-17 2.40.58 PM.png

`<!doctype html>




CO₂ Monitor — Arduino Uno R4 WiFi
<br> :root{<br> --bg: #fbfbfb;<br> --card: #ffffff;<br> --muted: #6b7280;<br> --accent: #0f766e;<br> --danger: #b91c1c;<br> --glass: rgba(15,118,110,0.08);<br> font-family: Inter, system-ui, -apple-system, &quot;Segoe UI&quot;, Roboto, &quot;Helvetica Neue&quot;, Arial;<br> }<br> html,body{height:100%;margin:0;background:linear-gradient(180deg,#f7f9fb,var(--bg));}<br> .wrap{<br> max-width:980px;<br> margin:28px auto;<br> padding:24px;<br> display:grid;<br> grid-template-columns: 1fr 360px;<br> gap:20px;<br> align-items:start;<br> }<br> .card{<br> background:var(--card);<br> border-radius:12px;<br> box-shadow:0 6px 20px rgba(16,24,40,0.06);<br> padding:20px;<br> }<br> .header{<br> display:flex;<br> gap:16px;<br> align-items:center;<br> }<br> .title{<br> font-size:20px;<br> font-weight:700;<br> color:#0f172a;<br> }<br> .subtitle{color:var(--muted);font-size:13px}<br> .big-readout{<br> margin-top:18px;<br> display:flex;<br> align-items:baseline;<br> gap:14px;<br> }<br> .ppm{<br> font-size:56px;<br> font-weight:800;<br> color:#0f172a;<br> letter-spacing:-1px;<br> }<br> .unit{<br> color:var(--muted);<br> font-weight:600;<br> font-size:14px;<br> }<br> .meta { margin-top:8px; color:var(--muted); font-size:13px }<br> .graph-wrap{ width:100%; height:220px; margin-top:18px; }<br> canvas{ width:100%; height:100%; display:block; border-radius:8px; background:linear-gradient(180deg, rgba(15,118,110,0.03), rgba(15,118,110,0.01)); box-shadow: inset 0 1px 0 rgba(255,255,255,0.5);}<br> .side{<br> display:flex;<br> flex-direction:column;<br> gap:12px;<br> }<br> .stat{<br> padding:14px;<br> border-radius:10px;<br> background:var(--glass);<br> display:flex;<br> flex-direction:column;<br> gap:6px;<br> }<br> .stat .label{font-size:12px;color:var(--muted)}<br> .stat .value{font-weight:700;font-size:20px;color:#0f172a}<br> .footer-note{font-size:12px;color:var(--muted);margin-top:12px}<br> @media (max-width:880px){<br> .wrap{grid-template-columns:1fr; padding:16px}<br> .side{flex-direction:row; gap:10px}<br> .side .stat{flex:1}<br> }<br>






CO₂ Monitor

Arduino Uno R4 WiFi • K30 sensor • Live data


  <div class="big-readout" aria-live="polite">
    <div class="ppm" id="co2Value">—</div>
    <div class="unit">ppm</div>
  </div>
  <div class="meta" id="lastSeen">waiting for data…</div>

  <div class="graph-wrap card" style="margin:0; padding:12px; box-shadow:none;">
    <canvas id="chart" width="600" height="220" role="img" aria-label="CO2 trend chart"></canvas>
  </div>

  <div class="footer-note">Note: page served from the Arduino's IP. You must be on the same Wi-Fi network to view it.</div>
</section>

<aside class="side"><div class="stat card">
    <div class="label">Device</div>
    <div class="value">Arduino Uno R4 WiFi</div>
  </div>
  <div class="stat card">
    <div class="label">Sensor</div>
    <div class="value">K30 (0–10,000 ppm)</div>
  </div>
  <div class="stat card">
    <div class="label">Power</div>
    <div class="value">5–9 W solar panel</div>
  </div>


<br> // Configuration: endpoint the Arduino serves. Adjust if your sketch uses a different route.<br> const ENDPOINT = &#39;/co2&#39;; // e.g. <a href="http://192.168.1.53/co2" target="_blank" rel="nofollow noopener">http://192.168.1.53/co2</a> when testing from another device<br> const POLL<em>INTERVAL = 5000; // ms<br> const MAX</em>POINTS = 60; // number of points shown in chart</p> <pre><code class="prettyprint">// DOM const co2El = document.getElementById(&#39;co2Value&#39;); const lastSeenEl = document.getElementById(&#39;lastSeen&#39;); const canvas = document.getElementById(&#39;chart&#39;); const ctx = canvas.getContext(&#39;2d&#39;); // Data store let readings = []; // {t:timestamp, v:value} // Helpers function isoTime(ts=Date.now()){ return new Date(ts).toLocaleTimeString(); } async function fetchCO2(){ try{ const res = await fetch(ENDPOINT, {cache:&#39;no-store&#39;}); if(!res.ok) throw new Error(&#39;HTTP &#39; + res.status); const data = await res.json(); // expect {co2: 415} or {&quot;co2&quot;: 415} const value = Number(data.co2 ?? data.co2_ppm ?? data.value); if(Number.isFinite(value)){ pushReading(value); updateUI(value); } else { console.warn(&#39;Unexpected payload&#39;, data); lastSeenEl.textContent = &#39;Unexpected response from device&#39;; } } catch(err){ console.error(&#39;Fetch failed&#39;, err); lastSeenEl.textContent = &#39;Unable to reach device — are you on the same Wi-Fi?&#39;; } } function pushReading(v){ readings.push({t:Date.now(), v: Math.round(v)}); if(readings.length &gt; MAX_POINTS) readings.shift(); drawChart(); } function updateUI(v){ co2El.textContent = Math.round(v); lastSeenEl.textContent = `Last update: ${isoTime()}`; } // Simple line chart drawn on canvas function drawChart(){ const W = canvas.width = canvas.clientWidth * devicePixelRatio; const H = canvas.height = canvas.clientHeight * devicePixelRatio; ctx.clearRect(0,0,W,H); if(readings.length === 0) { // placeholder grid drawGrid(W,H); ctx.fillStyle = &#39;#7c8a93&#39;; ctx.font = `${12*devicePixelRatio}px sans-serif`; ctx.fillText(&#39;No data yet&#39;, W/2 - 30*devicePixelRatio, H/2); return; } // compute min/max with some padding const values = readings.map(r =&gt; r.v); let min = Math.min(...values); let max = Math.max(...values); if (min === max) { min = Math.max(0, min - 50); max = max + 50; } const padding = 0.08 * (max - min); min = min - padding; max = max + padding; drawGrid(W,H, min, max); // coords const left = 8*devicePixelRatio; const right = W - 8*devicePixelRatio; const top = 8*devicePixelRatio; const bottom = H - 20*devicePixelRatio; const width = right - left; const height = bottom - top; ctx.lineWidth = 2*devicePixelRatio; ctx.lineJoin = &#39;round&#39;; ctx.lineCap = &#39;round&#39;; ctx.beginPath(); readings.forEach((pt, i) =&gt; { const x = left + (i / Math.max(1, readings.length-1)) * width; const y = top + ((max - pt.v) / (max - min)) * height; if(i===0) ctx.moveTo(x,y); else ctx.lineTo(x,y); }); ctx.strokeStyle = &#39;#0f766e&#39;; ctx.stroke(); // fill under curve ctx.lineTo(right, bottom); ctx.lineTo(left, bottom); ctx.closePath(); ctx.fillStyle = &#39;rgba(15,118,110,0.08)&#39;; ctx.fill(); // right-side current value label const last = readings[readings.length-1]; const lastX = left + (readings.length-1) / Math.max(1, readings.length-1) * width; const lastY = top + ((max - last.v) / (max - min)) * height; ctx.fillStyle = &#39;#0f172a&#39;; ctx.font = `${12*devicePixelRatio}px sans-serif`; ctx.fillText(`${last.v} ppm`, Math.min(lastX + 8*devicePixelRatio, right - 60*devicePixelRatio), lastY - 6*devicePixelRatio); } function drawGrid(W,H, min=0, max=1000){ // light background and horizontal ticks ctx.fillStyle = &#39;rgba(255,255,255,0.0)&#39;; ctx.fillRect(0,0,W,H); ctx.strokeStyle = &#39;rgba(15,23,42,0.06)&#39;; ctx.lineWidth = 1 * devicePixelRatio; const ticks = 4; for(let i=0;i&lt;=ticks;i++){ const y = (i/ticks) * (H - 32*devicePixelRatio) + 8*devicePixelRatio; ctx.beginPath(); ctx.moveTo(8*devicePixelRatio, y); ctx.lineTo(W - 8*devicePixelRatio, y); ctx.stroke(); // label const value = Math.round(max - (i/ticks)*(max-min)); ctx.fillStyle = &#39;rgba(15,23,42,0.45)&#39;; ctx.font = `${11*devicePixelRatio}px sans-serif`; ctx.fillText(String(value), 10*devicePixelRatio, y - 4*devicePixelRatio); } } // Kick off fetchCO2(); // initial setInterval(fetchCO2, POLL_INTERVAL); // For demos where you don&#39;t have an Arduino handy, // uncomment the block below to simulate readings: /* setInterval(()=&gt; { const sim = 380 + Math.round(80*Math.sin(Date.now()/60000) + Math.random()*30); pushReading(sim); updateUI(sim); }, 2000); */ </code></pre> <p>


`

Ethan Ethan added to the journal ago

Custom HTML Page with Arduino Web page

To connect the Arduino Uno R4 WiFi to the K30 CO₂ sensor, I used a UART (serial) communication setup. The K30’s TX pin was connected to the Arduino’s RX pin (12), and the K30’s RX pin to the Arduino’s TX pin (13), the K30 ground connected to the Arduino ground, and the K30 VCC connected to the Arduino 5V (when using a Arduino uno R3 or some other boards you may need an external power supply). Using the Arduino’s hardware serial interface, I wrote a simple sketch to request CO₂ concentration data from the sensor and display the live readings on the Serial Monitor. This allowed real-time monitoring of CO₂ levels directly through the Arduino IDE, ensuring reliable data exchange between the sensor and microcontroller.Screenshot 2025-10-17 2.33.43 PM.png

Ethan Ethan started CO2 Monitor ago