RajivCodeLabRajiv Sharma
Published © LGPL

How to Build Web Socket on Raspberry Pi Pico W and Make Live

By the end of this tutorial, you'll have a dashboard displaying the temperature readings from your Raspberry Pi Pico W using Web Socket.

IntermediateProtip2 hours124
How to Build Web Socket on Raspberry Pi Pico W and Make Live

Things used in this project

Hardware components

Raspberry Pi Pico W
Raspberry Pi Pico W
×1

Software apps and online services

Thonny IDE
Micro Dot Library

Story

Read more

Code

index.html (UI Page)

HTML
<!DOCTYPE html>
<html>
<head>
  <title>Live Temperature Dashboard</title>
  <meta charset="UTF-8">
  <!-- Include Chart.js library -->
  <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
  <style>
 
@import url(https://fonts.googleapis.com/css?family=Open+Sans:700,300);

 
.frame {
  position: absolute;
  top: 50%;
  left: 7%;
  width: 400px;
  height: 400px;
  margin-top: -200px;
  margin-left: -200px;
  border-radius: 2px;
	
	overflow: hidden;
  background: #222;
  color: #333;
	font-family: 'Open Sans', Helvetica, sans-serif;
	-webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;
	background: #222;
}

.center {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%,-50%);
}

.temparature-meter__container {
	position: relative;
	height: 200px;
	width: 200px;
	border-radius: 200px;
	box-shadow: 0 1px 2px rgba(0, 0, 0, .07);
	border: solid 10px #fff;
	overflow: hidden;
	
	&:after {
		position: absolute;
		content: "";
		height: 120px;
		width: 120px;
		background-color: #fff;
		z-index: 0;
		transform: rotate(45deg);
		bottom: -40px;
		left: 40px;
		z-index: 1;
	}
}

.temparature-meter__inner {
	display: flex;
	text-align: center;
	height: 100%;
	width: 100%;
	border-radius: 100%;
	box-shadow: inset 0 0 10px rgba(0, 0, 0, .4);
	background: linear-gradient(to right, #148d04 0%,#f52c3d 100%);
	overflow: hidden;
	
	.temparature-meter__inner-ring {
		cursor: pointer;
		position: relative;
		margin: auto;
		display: flex;
        flex-direction: column;
        justify-content: center;
		height: 130px;
		width: 130px;
		background-color: #fff;
		border-radius: 100%;
		box-shadow: 1px 2px 4px 0 rgba(0, 0, 0, 0.2);
		z-index: 9;
		
		&:after {
			content: "";
			position: absolute;
			width: 1px;
			height: 50%;
			background-image: linear-gradient(#148d04 15px, transparent 0);
			top: 0;
			left: 50%;
			transform-origin: bottom;
			transform: rotatez(-35deg);
			transition: all 0.8s ease-in-out;
		}
		
		&:hover:after {
			transform: rotatez(-20deg);
		}
		
		.temparature-count {
			display: block;
			position: absolute;
			width: 100%;
			text-align: center;
			transition: all 0.8s ease-in-out;
			
			&.c2 {
				opacity: 0;
				transform: translatex(50px) scale(0.5);
			}
		}
		
		&:hover {
			.c1 {
				opacity: 0;
				transform: translatex(-50px) scale(0.5);
			}
			.c2 {
				opacity: 1;
				transform: translatex(0) scale(1);
			}
		}
	}
	
	.temparature-inside {
		position: relative;
		font-size: 50px;
    display: inline-block;
    letter-spacing: -5px;
    font-weight: 500;
    color: #148d04;
		height: 58px;
	
		.temperature-sup {
			font-size: 20px;
			position: absolute;
			position: relative;
    	right: -45px;
    	top: -30px;
		}
	}
	
	.temparature-outside {
		font-size: 13px;
	}
	
	.temparature-meter__inner-label {
		font-size: 13px;
	}
}


    body {
      background-color: #222;
      color: #fff;
      font-family: Arial, sans-serif;
     
    }
    h1 {
      text-align: center;
      color: #148d04;
      
    }
    .chart-container {
      margin-left: 300px;
      margin-top:20px;
      text-align:center;
      height: 70vh; /* Set the height to half of the viewport height */
    }
    header {
      background-color: #333;
      color: #fff;
      padding: 10px;
      text-align: center;
       /* Remove default margin */
      display: flex; /* Use flexbox to align items */
      align-items: center; /* Vertically center items */
    }
    #temperature {
      text-align: center;
      font-size: 24px;
      margin-bottom: 20px;
      margin-top:30px;
    }
    canvas {
     
      text-align:center;
      width:100%;
     
    }
    .logo {
      width: 50px; /* Adjust the width of the logo */
      height: auto; /* Maintain aspect ratio */
      margin-right: 10px; /* Add some space between logo and title */
      margin-left: 35px;
    }
  </style>
</head>
<body>
  <header>
    <img src="data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgZGF0YS1uYW1lPSJMYXllciAxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA3MS42OSA5Mi4xMDkiPjxkZWZzPjxzdHlsZT4uY2xzLTF7ZmlsbDojNDZhZjRifS5jbHMtMntmaWxsOiNjZDIzNTV9PC9zdHlsZT48L2RlZnM+PHBhdGggZD0iTTcxLjUzIDUyLjk4Yy0uNDEtNC4yNjYtMi4zMjgtOC4wNi01LjA1MS0xMC4wNmExMi40NzMgMTIuNDczIDAgMCAwLTIuMzQ0LTQuNTYgMTMuOTg2IDEzLjk4NiAwIDAgMC00Ljk3Ny05LjQgNS4yNTUgNS4yNTUgMCAwIDAgMS4xNTItMS40MDVjMy4zMDYtMS40NzYgNS42MzYtNC41NzYgNS45MDctNi44ODFhOS4wNDUgOS4wNDUgMCAwIDAgMi44NTctNS45MjUgNS42NjUgNS42NjUgMCAwIDAtLjUwNi0yLjY2NyA1LjA2OCA1LjA2OCAwIDAgMCAxLjMyMS01LjIzYy0uOTg3LTIuODYtNC40NDQtNC43MTUtOS4wODgtNC45MTJBNy4zMiA3LjMyIDAgMCAwIDU1Ljc0LjIwOGExMS42OTkgMTEuNjk5IDAgMCAwLTIuNzMyLjMzNkE2LjIxOSA2LjIxOSAwIDAgMCA1MC4zMjIgMCAxMC41MSAxMC41MSAwIDAgMCA0NS4yIDEuMzk3YTQuNzA5IDQuNzA5IDAgMCAwLS42MTgtLjA0Yy0yLjMwNiAwLTQuNjEgMS41ODgtNS43NzkgMi41MzVhMTcuNTIgMTcuNTIgMCAwIDAtMy4wMzcgMy4xNjQgMTcuNTI0IDE3LjUyNCAwIDAgMC0zLjAzNy0zLjE2NGMtMS4xNjktLjk0Ny0zLjQ3My0yLjUzNS01Ljc3OS0yLjUzNmE0LjcxMiA0LjcxMiAwIDAgMC0uNjE4LjA0QTEwLjUxMiAxMC41MTIgMCAwIDAgMjEuMjA4IDBhNi4yMiA2LjIyIDAgMCAwLTIuNjg2LjU0NEExMS42OTggMTEuNjk4IDAgMCAwIDE1Ljc5LjIwOGE3LjMyIDcuMzIgMCAwIDAtNS4wNiAxLjczMmMtNC42NDUuMTk3LTguMTAzIDIuMDUzLTkuMDkgNC45MTJhNS4wNjggNS4wNjggMCAwIDAgMS4zMjEgNS4yMyA1LjY2NyA1LjY2NyAwIDAgMC0uNTA1IDIuNjY3IDkuMDQ0IDkuMDQ0IDAgMCAwIDIuODU3IDUuOTI1Yy4yNyAyLjMwNSAyLjYwMSA1LjQwNSA1LjkwNiA2Ljg4MmE1LjI1MyA1LjI1MyAwIDAgMCAxLjE1MiAxLjQwMyAxMy45ODcgMTMuOTg3IDAgMCAwLTQuOTc2IDkuNDAyIDEyLjQ3MSAxMi40NzEgMCAwIDAtMi4zNDQgNC41NTlDMi4zMjggNDQuOTIuNDEgNDguNzE0IDAgNTIuOThjLS40MDggNC4yNC43NTUgOC4xOTggMy4xMjMgMTAuNzA1YTE1LjE1OCAxNS4xNTggMCAwIDAgMi41NzQgNS45MTFBMTUuMzgzIDE1LjM4MyAwIDAgMCA5LjU4IDc4LjU3YTE1Ljg5NyAxNS44OTcgMCAwIDAgOC41NjQgNS4wODkgMjAuNzMzIDIwLjczMyAwIDAgMCA2Ljg0OCA0LjI0IDE1Ljg4OCAxNS44ODggMCAwIDAgMjEuNTQ2IDAgMjAuNzM4IDIwLjczOCAwIDAgMCA2Ljg0OC00LjI0IDE1Ljg5NyAxNS44OTcgMCAwIDAgOC41NjMtNS4wODkgMTUuMzgzIDE1LjM4MyAwIDAgMCAzLjg4NC04Ljk3MiAxNS4xNTQgMTUuMTU0IDAgMCAwIDIuNTc0LTUuOTEyYzIuMzY3LTIuNTA3IDMuNTMtNi40NjUgMy4xMjMtMTAuNzA1WiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLjA4IC4wMDIpIi8+PHBhdGggY2xhc3M9ImNscy0xIiBkPSJNNDQuNDQ2IDYuMThhLjM2Ni4zNjYgMCAwIDEgLjQ2NS4zOWMtLjExNiAxLjA5NS41NDcuOTU3LjcwMy43NjJhNS43MzggNS43MzggMCAwIDEgNS4xODktMi40ODkuMzY2LjM2NiAwIDAgMSAuMjUuNTljLS42NS45NjUuMDUgMS4xNDguMzEuOTc1IDIuNjU0LTEuNjYgNS4xODctMS42NjMgNi4xNi0uOTY2YS4zNjYuMzY2IDAgMCAxIC4wMjEuNTY5Yy0uOTgzLjgzNy0uNDMyIDEuMjU0LS4wODUgMS4xMjcgMi42OTUtLjk1MiA2LjQxNi0uMTEgNy43MTUuOTg3YS4zNjkuMzY5IDAgMCAxIC4wMDkuNTU5IDcuMzEyIDcuMzEyIDAgMCAwLTIuODE1IDQuOTFjLS4wNzMuNjQ2Ljk5OC41MSAxLjQ1LjM5M2EuMzY2LjM2NiAwIDAgMSAuNDU2LjM0MmMuMDQxIDEuNTUyLTEuNDI3IDMuMjU4LTMuNjEgNC43MTctLjI5My4xOTUtLjI1MS42NjQuNDE2LjY4MWEuMzYyLjM2MiAwIDAgMSAuMzExLjU0NWMtLjc3OSAxLjQ0NS0xLjgzOCAyLjgwNi01LjQ0NSAzLjc0YS4zNy4zNyAwIDAgMC0uMDUuNjk4LjM1NC4zNTQgMCAwIDEgLjExNS42MjVjLTMuNTkyIDMuMDg1LTEyLjY5NSAxLjg2OS0xMy44NS0zLjMyNGEuMzY1LjM2NSAwIDAgMSAuMDY1LS4yOTcgMzQuNjAzIDM0LjYwMyAwIDAgMSAxNS4wOTYtMTEuNDg2LjI4Ny4yODcgMCAwIDAtLjE0Mi0uNTUzIDI0LjY4NSAyNC42ODUgMCAwIDAtMTYuMzk0IDkuODg0LjM3LjM3IDAgMCAxLS40Ny4xMDZjLTUuODM1LTMuMDEyLTEuMDQzLTExLjkwOCA0LjEzLTEzLjQ4NVpNMTAuMTM5IDIwLjI3MmEuMzYyLjM2MiAwIDAgMSAuMzEtLjU0NWMuNjY4LS4wMTcuNzEtLjQ4Ni40MTctLjY4MS0yLjE4NC0xLjQ2LTMuNjUxLTMuMTY1LTMuNjEtNC43MTdhLjM2Ni4zNjYgMCAwIDEgLjQ1Ni0uMzQyYy40NTIuMTE3IDEuNTIzLjI1MyAxLjQ1LS4zOTNhNy4zMTIgNy4zMTIgMCAwIDAtMi44MTUtNC45MS4zNjkuMzY5IDAgMCAxIC4wMS0uNTU5YzEuMjk3LTEuMDk3IDUuMDE5LTEuOTM5IDcuNzE0LS45ODcuMzQ3LjEyNy44OTctLjI5LS4wODUtMS4xMjdhLjM2Ni4zNjYgMCAwIDEgLjAyMi0uNTY5Yy45NzItLjY5NyAzLjUwNC0uNjk0IDYuMTYuOTY2LjI1OS4xNzMuOTU5LS4wMS4zMDktLjk3NGEuMzY2LjM2NiAwIDAgMSAuMjUtLjU5MSA1LjczOCA1LjczOCAwIDAgMSA1LjE4OSAyLjQ4OWMuMTU2LjE5NS44MTkuMzMzLjcwMy0uNzYzYS4zNjYuMzY2IDAgMCAxIC40NjUtLjM4OWM1LjE3MyAxLjU3NyA5Ljk2NCAxMC40NzMgNC4xMyAxMy40ODVhLjM3LjM3IDAgMCAxLS40Ny0uMTA2QTI0LjY4NSAyNC42ODUgMCAwIDAgMTQuMzUgOS42NzVhLjI4Ny4yODcgMCAwIDAtLjE0Mi41NTMgMzQuNjAzIDM0LjYwMyAwIDAgMSAxNS4wOTUgMTEuNDg2LjM2NC4zNjQgMCAwIDEgLjA2Ni4yOTdjLTEuMTU2IDUuMTkzLTEwLjI1OSA2LjQwOS0xMy44NSAzLjMyNGEuMzU0LjM1NCAwIDAgMSAuMTE1LS42MjUuMzcuMzcgMCAwIDAtLjA1LS42OThjLTMuNjA3LS45MzQtNC42NjYtMi4yOTUtNS40NDUtMy43NFoiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC4wOCAuMDAyKSIvPjxwYXRoIGNsYXNzPSJjbHMtMiIgZD0iTTU4Ljc1NCAzNi4xNDZjMS40OTYgMy45OC4zMyA1Ljg5NC0zLjIxOSAzLjg2OGEyNS40NCAyNS40NCAwIDAgMS03LjQ1NC02LjI0OWMtMi42NzQtMy4xNzYtLjkzNi00LjU5MSAzLjI3NS0zLjgyOGExMS4zNjggMTEuMzY4IDAgMCAxIDcuMzk4IDYuMjA5Wk00Mi44MzQgMzEuNjY4YzUuMDc2IDQuMjE5LS43NCA3LjIwNS03LjA3IDcuMjA1cy0xMi4xNDQtMi45ODYtNy4wNjgtNy4yMDVhMTEuMjg2IDExLjI4NiAwIDAgMSAxNC4xMzggMFpNMTIuNzc2IDM2LjE0NmExMS4zNjggMTEuMzY4IDAgMCAxIDcuMzk4LTYuMjFjNC4yMTEtLjc2MiA1Ljk0OS42NTMgMy4yNzUgMy44M2EyNS40NCAyNS40NCAwIDAgMS03LjQ1NCA2LjI0OGMtMy41NSAyLjAyNi00LjcxNi4xMTItMy4yMi0zLjg2OFpNNS40MzMgNTguMjY0YTExLjM2NCAxMS4zNjQgMCAwIDEgLjg0NC05LjY3YzEuODc1LTMuMTkxIDMuNzYzLTMuMDY0IDQuNTEyLS4zNmExNi45NTQgMTYuOTU0IDAgMCAxLS45NzcgMTEuMTc0Yy0xLjM4OSAyLjc2LTMuMTI2IDEuODI3LTQuMzc5LTEuMTQ0Wk0xOC45NTcgNzguOTMyYTExLjI5NyAxMS4yOTcgMCAwIDEtOC40OTgtMTAuMDgyYy43MDQtMTYuMzgyIDI0LjMxNCAxMi44NzQgOC40OTggMTAuMDgyWk0xOS40MzcgNTguNTI4Yy0zLjk1OS0yLjI4NS01LjAwNy03Ljg4My0yLjM0LTEyLjUwMnM4LjAzOS02LjUxMSAxMS45OTgtNC4yMjUgNS4wMDcgNy44ODQgMi4zNCAxMi41MDMtOC4wMzkgNi41MS0xMS45OTggNC4yMjRaTTQyLjM3MiA4NS4xODRhMTEuMzA3IDExLjMwNyAwIDAgMS0xMy4yMTQgMGMtMy45MjMtMi43MDkuMDI0LTUuODI2IDYuNjA3LTUuODI2czEwLjUzIDMuMTE3IDYuNjA3IDUuODI2WiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLjA4IC4wMDIpIi8+PGVsbGlwc2UgY2xhc3M9ImNscy0yIiBjeD0iMzUuODQ1IiBjeT0iNjcuMzc4IiByeD0iOS4xOSIgcnk9IjcuODc3Ii8+PHBhdGggY2xhc3M9ImNscy0yIiBkPSJNNDAuMDk1IDU0LjMwNGMtMi42NjctNC42Mi0xLjYyLTEwLjIxNyAyLjM0LTEyLjUwM3M5LjMzLS4zOTQgMTEuOTk4IDQuMjI1IDEuNjE5IDEwLjIxNy0yLjM0IDEyLjUwMmMtMy45NiAyLjI4Ni05LjMzMS4zOTUtMTEuOTk4LTQuMjI0Wk01Mi41NzMgNzguOTMyYy0xNS44MTYgMi43OTIgNy43OTQtMjYuNDY0IDguNDk4LTEwLjA4MmExMS4yOTcgMTEuMjk3IDAgMCAxLTguNDk4IDEwLjA4MlpNNjYuMDk2IDU4LjI2NGMtMS4yNTIgMi45NzEtMi45OSAzLjkwNC00LjM3OCAxLjE0NGExNi45NTQgMTYuOTU0IDAgMCAxLS45NzctMTEuMTc1Yy43NDktMi43MDMgMi42MzYtMi44MyA0LjUxMi4zNmExMS4zNjQgMTEuMzY0IDAgMCAxIC44NDMgOS42NzFaIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSguMDggLjAwMikiLz48L3N2Zz4=" class="logo">
    <h1>Live Temperature Dashboard</h1>
  </header>
  <div class="frame">
  <div class="center">
		<div class="temparature-meter__container">
			<div class="temparature-meter__inner">
				<div class="temparature-meter__inner-ring">
					 
				<div class="temparature-inside">
						<span id="temp-meter" class="temparature-count c1"></span>
						 
						<span class="temperature-sup"></span>
					</div>
					<div class="temparature-meter__inner-label">Bedroom</div>
				</div>
			</div>
		</div>
  </div>
</div>
  <div id="temperature">Temperature: --</div>
  <div class="chart-container">
    <canvas id="temperatureGraph"></canvas>
  </div>
 

  <script>
    const socket = new WebSocket('ws://' + location.host + '/temperature');
    let chart; // Reference to the chart object

    socket.addEventListener('message', ev => {
     console.log(ev);
      const temperature = parseFloat(ev.data);
      updateGraph('temperatureGraph', 'Temperature (C)', temperature);
      updateTemperatureDisplay(temperature);
    });

    function updateTemperatureDisplay(temperature) {
      document.getElementById('temperature').textContent = `Temperature: ${temperature.toFixed(2)} C`;
      document.getElementById('temp-meter').textContent = temperature.toFixed(2);
    }

    function updateGraph(chartId, label, value) {
      const ctx = document.getElementById(chartId).getContext('2d');

      if (!chart) {
        chart = new Chart(ctx, {
          type: 'line',
          data: {
            labels: Array.from({ length: 15 }, (_, i) => i + 1),
            datasets: [{
              label: label,
              data: Array(25).fill(value), // Fills array with 10 same values for initial chart setup
              fill: true,
              borderColor: '#148d04',
              tension: 0.4
            }]
          },
          options: {
            scales: {
              x: {
                title: {
                  display: true,
                  text: 'Time',
                  color: '#ccc'
                },
                grid: {
                  color: '#444'
                },
                ticks: {
                  color: '#ccc'
                }
              },
              y: {
                title: {
                  display: true,
                  text: label,
                  color: '#ccc'
                },
                grid: {
                  color: '#444'
                },
                ticks: {
                  color: '#ccc'
                }
              }
            },
            plugins: {
              legend: {
                labels: {
                  color: '#ccc'
                }
              }
            }
          }
        });
      } else {
        // Update the existing chart data
        chart.data.labels.push(new Date().toLocaleTimeString());
        chart.data.datasets[0].data.push(value);
        if (chart.data.labels.length > 10) {
          chart.data.labels.shift(); // Remove the oldest label
          chart.data.datasets[0].data.shift(); // Remove the oldest data point
        }
        chart.update();
      }
    }
  </script>
</body>
</html>

Python Code

Python
import machine
import time
import network
from microdot import Microdot, send_file
from microdot.websocket import with_websocket

def connect_to_wifi():
    sta_if = network.WLAN(network.STA_IF)
    if not sta_if.isconnected():
        print("Connecting to the network...")
        sta_if.active(True)
        sta_if.connect("Airtel_RAJEEV", "00351553")
        while not sta_if.isconnected():
            pass
    print("Connected to IP: ", sta_if.ifconfig()[0])

connect_to_wifi()

app = Microdot()

@app.route('/')
async def index(request):
    return send_file("index.html")

@app.route('/api/led', methods=['POST'])
async def index(request):
    print(request.json)
    print(request.json["ledRed"])
    return {'success': True}

@app.route('/temperature')
@with_websocket
async def index(request, ws):
    while True:
        adc = machine.ADC(4)  # Use ADC pin GP4
        conversion_factor = 3.3 / (65535)  # ADC conversion factor
        sensor_value = adc.read_u16() * conversion_factor
        temperature = 27 - (sensor_value - 0.706) / 0.001721  # Convert sensor value to temperature (formula may vary)
        await ws.send(str(temperature))
        time.sleep(1)

app.run(debug= True, port=80)

Credits

RajivCodeLab

RajivCodeLab

6 projects • 3 followers
Creates YT videos on DIY IoT Projects: Raspberry Pi Pico, Raspberry Pi Zero, Arduino, ESP32,
Rajiv Sharma

Rajiv Sharma

17 projects • 71 followers
Having more than 10 years of experience in IoT and software technology. Founded IoTBoys to share knowledge with IoT enthusiasts.

Comments