GPIO
Time to do some real physical computing with the pi. This is my favorite part. If you look at the pi, you'll see a bunch of pins on the side. These are the GPIO (General Purpose Input Output) pins. You can use these pins to interact with the physical world. You can read sensors, control motors, turn on lights, whatever.
I'll show you a super quick example, then i'll explain more.
Checking the pins
You can check what each pin on the raspberry pi is for by running the following command in the raspberry pi terminal:
pinout
Blinking LED
- Connect the long leg of the LED to pin 17
- Connect the short leg of the LED to a 50 ohm resistor
- Connect the other end of the resistor to a ground pin
from gpiozero import LED
from time import sleep
led = LED(17)
while True:
led.on()
sleep(1)
led.off()
sleep(1)
from gpiozero import LED
from time import sleep
led = LED(17)
while True:
led.on()
sleep(1)
led.off()
sleep(1)
from gpiozero import LED
from time import sleep
led = LED(17)
while True:
led.on()
sleep(1)
led.off()
sleep(1)
from gpiozero import LED
from time import sleep
led = LED(17)
while True:
led.on()
sleep(1)
led.off()
sleep(1)
Next we'll move to examples from https://gpiozero.readthedocs.io/en/stable/recipes.html
from gpiozero import PWMLED
from time import sleep
led = PWMLED(17)
while True:
for i in range(100):
led.value = i/100
sleep(0.1)
for i in range(100):
led.value = 1 - i/100
sleep(0.1)
from gpiozero import PWMLED
from time import sleep
led = PWMLED(17)
while True:
for i in range(100):
led.value = i/100
sleep(0.1)
for i in range(100):
led.value = 1 - i/100
sleep(0.1)
from gpiozero import PWMLED
from time import sleep
led = PWMLED(17)
while True:
for i in range(100):
led.value = i/100
sleep(0.1)
for i in range(100):
led.value = 1 - i/100
sleep(0.1)
from gpiozero import PWMLED
from time import sleep
led = PWMLED(17)
while True:
for i in range(100):
led.value = i/100
sleep(0.1)
for i in range(100):
led.value = 1 - i/100
sleep(0.1)
Let's put this into a server so we can control it from the react app.
from fastapi import FastAPI
from gpiozero import PWMLED
from contextlib import asynccontextmanager
@asynccontextmanager
async def lifespan(app: FastAPI):
global led
led = PWMLED(17)
yield
app = FastAPI(lifespan=lifespan)
@app.put("/brightness/{brightness}")
def update_item(brightness: float):
led.value = brightness/100
return {"brightness": brightness}
from fastapi import FastAPI
from gpiozero import PWMLED
from contextlib import asynccontextmanager
@asynccontextmanager
async def lifespan(app: FastAPI):
global led
led = PWMLED(17)
yield
app = FastAPI(lifespan=lifespan)
@app.put("/brightness/{brightness}")
def update_item(brightness: float):
led.value = brightness/100
return {"brightness": brightness}
from fastapi import FastAPI
from gpiozero import PWMLED
from contextlib import asynccontextmanager
@asynccontextmanager
async def lifespan(app: FastAPI):
global led
led = PWMLED(17)
yield
app = FastAPI(lifespan=lifespan)
@app.put("/brightness/{brightness}")
def update_item(brightness: float):
led.value = brightness/100
return {"brightness": brightness}
from fastapi import FastAPI
from gpiozero import PWMLED
from contextlib import asynccontextmanager
@asynccontextmanager
async def lifespan(app: FastAPI):
global led
led = PWMLED(17)
yield
app = FastAPI(lifespan=lifespan)
@app.put("/brightness/{brightness}")
def update_item(brightness: float):
led.value = brightness/100
return {"brightness": brightness}
npx shadcn-ui@latest add slider
npx shadcn-ui@latest add slider
npx shadcn-ui@latest add slider
npx shadcn-ui@latest add slider
"use client"
import { useState } from "react";
import { Slider } from "@/components/ui/slider";
export default function Controls() {
const [brightness, setBrightness] = useState(0);
const handleBrightnessChange = async (value: number[]) => {
const brightness = value[0]
setBrightness(brightness);
await fetch(`/py/brightness/${brightness}`, {
method: "PUT"
})
};
return (
<div className="w-full max-w-md p-4 bg-card rounded-md shadow-md">
<h3 className="text-lg font-semibold text-foreground mb-2">Light Brightness Control</h3>
<Slider
value={[brightness]}
onValueChange={handleBrightnessChange}
min={0}
max={100}
className="w-full"
/>
<div className="text-center text-sm text-foreground font-medium mt-2">
{brightness}%
</div>
</div>
);
}
"use client"
import { useState } from "react";
import { Slider } from "@/components/ui/slider";
export default function Controls() {
const [brightness, setBrightness] = useState(0);
const handleBrightnessChange = async (value: number[]) => {
const brightness = value[0]
setBrightness(brightness);
await fetch(`/py/brightness/${brightness}`, {
method: "PUT"
})
};
return (
<div className="w-full max-w-md p-4 bg-card rounded-md shadow-md">
<h3 className="text-lg font-semibold text-foreground mb-2">Light Brightness Control</h3>
<Slider
value={[brightness]}
onValueChange={handleBrightnessChange}
min={0}
max={100}
className="w-full"
/>
<div className="text-center text-sm text-foreground font-medium mt-2">
{brightness}%
</div>
</div>
);
}
"use client"
import { useState } from "react";
import { Slider } from "@/components/ui/slider";
export default function Controls() {
const [brightness, setBrightness] = useState(0);
const handleBrightnessChange = async (value: number[]) => {
const brightness = value[0]
setBrightness(brightness);
await fetch(`/py/brightness/${brightness}`, {
method: "PUT"
})
};
return (
<div className="w-full max-w-md p-4 bg-card rounded-md shadow-md">
<h3 className="text-lg font-semibold text-foreground mb-2">Light Brightness Control</h3>
<Slider
value={[brightness]}
onValueChange={handleBrightnessChange}
min={0}
max={100}
className="w-full"
/>
<div className="text-center text-sm text-foreground font-medium mt-2">
{brightness}%
</div>
</div>
);
}
"use client"
import { useState } from "react";
import { Slider } from "@/components/ui/slider";
export default function Controls() {
const [brightness, setBrightness] = useState(0);
const handleBrightnessChange = async (value: number[]) => {
const brightness = value[0]
setBrightness(brightness);
await fetch(`/py/brightness/${brightness}`, {
method: "PUT"
})
};
return (
<div className="w-full max-w-md p-4 bg-card rounded-md shadow-md">
<h3 className="text-lg font-semibold text-foreground mb-2">Light Brightness Control</h3>
<Slider
value={[brightness]}
onValueChange={handleBrightnessChange}
min={0}
max={100}
className="w-full"
/>
<div className="text-center text-sm text-foreground font-medium mt-2">
{brightness}%
</div>
</div>
);
}
Now you should be able to control the LED brightness from the react app.
Motor
Motors are just like leds, but they need a bigger power source.
- Connect the motor to pin 17
- Connect the other end of the motor to a ground pin
from fastapi import FastAPI
from contextlib import asynccontextmanager
from gpiozero import Motor, PWMOutputDevice
@asynccontextmanager
async def lifespan(app: FastAPI):
global motorSpeed, motor
motor = Motor(forward=27, backward=22)
motorSpeed = PWMOutputDevice(17)
motor.forward()
yield
app = FastAPI(lifespan=lifespan)
@app.put("/brightness/{brightness}")
def update_item(brightness: float):
motorSpeed.value = brightness/100
return {"brightness": brightness}
from fastapi import FastAPI
from contextlib import asynccontextmanager
from gpiozero import Motor, PWMOutputDevice
@asynccontextmanager
async def lifespan(app: FastAPI):
global motorSpeed, motor
motor = Motor(forward=27, backward=22)
motorSpeed = PWMOutputDevice(17)
motor.forward()
yield
app = FastAPI(lifespan=lifespan)
@app.put("/brightness/{brightness}")
def update_item(brightness: float):
motorSpeed.value = brightness/100
return {"brightness": brightness}
from fastapi import FastAPI
from contextlib import asynccontextmanager
from gpiozero import Motor, PWMOutputDevice
@asynccontextmanager
async def lifespan(app: FastAPI):
global motorSpeed, motor
motor = Motor(forward=27, backward=22)
motorSpeed = PWMOutputDevice(17)
motor.forward()
yield
app = FastAPI(lifespan=lifespan)
@app.put("/brightness/{brightness}")
def update_item(brightness: float):
motorSpeed.value = brightness/100
return {"brightness": brightness}
from fastapi import FastAPI
from contextlib import asynccontextmanager
from gpiozero import Motor, PWMOutputDevice
@asynccontextmanager
async def lifespan(app: FastAPI):
global motorSpeed, motor
motor = Motor(forward=27, backward=22)
motorSpeed = PWMOutputDevice(17)
motor.forward()
yield
app = FastAPI(lifespan=lifespan)
@app.put("/brightness/{brightness}")
def update_item(brightness: float):
motorSpeed.value = brightness/100
return {"brightness": brightness}
The react code can stay the same and you should now be able to control the motor speed from the app.
Distance Sensor
It's not just controlling things, you can also detect things from sensors. Let's add a distance sensor to the pi.
The circuit connects to two GPIO pins (one for echo, one for trigger), the ground pin, and a 5V pin. You'll need to use a pair of resistors (330Ω and 470Ω) as a potential divider.
from gpiozero import DistanceSensor
ultrasonic = DistanceSensor(echo=17, trigger=4)
while True:
distance = ultrasonic.distance * 100 # Convert to centimeters
print(f"{distance:.2f} cm")
from gpiozero import DistanceSensor
ultrasonic = DistanceSensor(echo=17, trigger=4)
while True:
distance = ultrasonic.distance * 100 # Convert to centimeters
print(f"{distance:.2f} cm")
from gpiozero import DistanceSensor
ultrasonic = DistanceSensor(echo=17, trigger=4)
while True:
distance = ultrasonic.distance * 100 # Convert to centimeters
print(f"{distance:.2f} cm")
from gpiozero import DistanceSensor
ultrasonic = DistanceSensor(echo=17, trigger=4)
while True:
distance = ultrasonic.distance * 100 # Convert to centimeters
print(f"{distance:.2f} cm")
from gpiozero import DistanceSensor
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
import asyncio
from contextlib import asynccontextmanager
@asynccontextmanager
async def lifespan(app: FastAPI):
global ultrasonic
ultrasonic = DistanceSensor(echo=17, trigger=4)
yield
app = FastAPI(lifespan=lifespan)
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
try:
while True:
await websocket.send_text(str(ultrasonic.distance))
await asyncio.sleep(0.01)
except WebSocketDisconnect:
print("disconnect")
from gpiozero import DistanceSensor
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
import asyncio
from contextlib import asynccontextmanager
@asynccontextmanager
async def lifespan(app: FastAPI):
global ultrasonic
ultrasonic = DistanceSensor(echo=17, trigger=4)
yield
app = FastAPI(lifespan=lifespan)
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
try:
while True:
await websocket.send_text(str(ultrasonic.distance))
await asyncio.sleep(0.01)
except WebSocketDisconnect:
print("disconnect")
from gpiozero import DistanceSensor
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
import asyncio
from contextlib import asynccontextmanager
@asynccontextmanager
async def lifespan(app: FastAPI):
global ultrasonic
ultrasonic = DistanceSensor(echo=17, trigger=4)
yield
app = FastAPI(lifespan=lifespan)
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
try:
while True:
await websocket.send_text(str(ultrasonic.distance))
await asyncio.sleep(0.01)
except WebSocketDisconnect:
print("disconnect")
from gpiozero import DistanceSensor
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
import asyncio
from contextlib import asynccontextmanager
@asynccontextmanager
async def lifespan(app: FastAPI):
global ultrasonic
ultrasonic = DistanceSensor(echo=17, trigger=4)
yield
app = FastAPI(lifespan=lifespan)
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
try:
while True:
await websocket.send_text(str(ultrasonic.distance))
await asyncio.sleep(0.01)
except WebSocketDisconnect:
print("disconnect")
"use client"
import {useState, useEffect} from "react"
import useWebSocket from "react-use-websocket"
import { Area, AreaChart } from "recharts"
import { ChartConfig, ChartContainer } from "@/components/ui/chart"
const chartConfig = {
distance: {
label: "Distance",
color: "#60a5fa",
}
} satisfies ChartConfig
export type Distance = {
distance: number
}
export function DistanceChart() {
const [distances, setDistances] = useState<Distance[]>([])
const socketUrl = "/py/ws";
const { lastMessage } = useWebSocket(socketUrl);
useEffect(() => {
if (lastMessage !== null) {
setDistances((prev) => prev.slice(-1000).concat({distance: Number(lastMessage.data)}));
}
}, [lastMessage]);
//
let distance = (lastMessage?.data * 100).toFixed(2) + " cm";
return (
<ChartContainer config={chartConfig} className="min-h-[200px] w-full">
<AreaChart accessibilityLayer data={distances}>
<Area dataKey="distance" fill="var(--color-distance)" radius={4} />
</AreaChart>
</ChartContainer>
)
}
"use client"
import {useState, useEffect} from "react"
import useWebSocket from "react-use-websocket"
import { Area, AreaChart } from "recharts"
import { ChartConfig, ChartContainer } from "@/components/ui/chart"
const chartConfig = {
distance: {
label: "Distance",
color: "#60a5fa",
}
} satisfies ChartConfig
export type Distance = {
distance: number
}
export function DistanceChart() {
const [distances, setDistances] = useState<Distance[]>([])
const socketUrl = "/py/ws";
const { lastMessage } = useWebSocket(socketUrl);
useEffect(() => {
if (lastMessage !== null) {
setDistances((prev) => prev.slice(-1000).concat({distance: Number(lastMessage.data)}));
}
}, [lastMessage]);
//
let distance = (lastMessage?.data * 100).toFixed(2) + " cm";
return (
<ChartContainer config={chartConfig} className="min-h-[200px] w-full">
<AreaChart accessibilityLayer data={distances}>
<Area dataKey="distance" fill="var(--color-distance)" radius={4} />
</AreaChart>
</ChartContainer>
)
}
"use client"
import {useState, useEffect} from "react"
import useWebSocket from "react-use-websocket"
import { Area, AreaChart } from "recharts"
import { ChartConfig, ChartContainer } from "@/components/ui/chart"
const chartConfig = {
distance: {
label: "Distance",
color: "#60a5fa",
}
} satisfies ChartConfig
export type Distance = {
distance: number
}
export function DistanceChart() {
const [distances, setDistances] = useState<Distance[]>([])
const socketUrl = "/py/ws";
const { lastMessage } = useWebSocket(socketUrl);
useEffect(() => {
if (lastMessage !== null) {
setDistances((prev) => prev.slice(-1000).concat({distance: Number(lastMessage.data)}));
}
}, [lastMessage]);
//
let distance = (lastMessage?.data * 100).toFixed(2) + " cm";
return (
<ChartContainer config={chartConfig} className="min-h-[200px] w-full">
<AreaChart accessibilityLayer data={distances}>
<Area dataKey="distance" fill="var(--color-distance)" radius={4} />
</AreaChart>
</ChartContainer>
)
}
"use client"
import {useState, useEffect} from "react"
import useWebSocket from "react-use-websocket"
import { Area, AreaChart } from "recharts"
import { ChartConfig, ChartContainer } from "@/components/ui/chart"
const chartConfig = {
distance: {
label: "Distance",
color: "#60a5fa",
}
} satisfies ChartConfig
export type Distance = {
distance: number
}
export function DistanceChart() {
const [distances, setDistances] = useState<Distance[]>([])
const socketUrl = "/py/ws";
const { lastMessage } = useWebSocket(socketUrl);
useEffect(() => {
if (lastMessage !== null) {
setDistances((prev) => prev.slice(-1000).concat({distance: Number(lastMessage.data)}));
}
}, [lastMessage]);
//
let distance = (lastMessage?.data * 100).toFixed(2) + " cm";
return (
<ChartContainer config={chartConfig} className="min-h-[200px] w-full">
<AreaChart accessibilityLayer data={distances}>
<Area dataKey="distance" fill="var(--color-distance)" radius={4} />
</AreaChart>
</ChartContainer>
)
}