Welcome to Hackster!
Hackster is a community dedicated to learning hardware, from beginner to pro. Join us, it's free!
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 hours3,210
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
7 projects • 5 followers
Creates YT videos on DIY IoT Projects: Raspberry Pi Pico, Raspberry Pi Zero, Arduino, ESP32,
Contact
Rajiv Sharma
18 projects • 73 followers
Having more than 10 years of experience in IoT and software technology. Founded IoTBoys to share knowledge with IoT enthusiasts.
Contact

Comments

Please log in or sign up to comment.