惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

云风的 BLOG
云风的 BLOG
IT之家
IT之家
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
V
Visual Studio Blog
博客园 - 司徒正美
美团技术团队
Last Week in AI
Last Week in AI
月光博客
月光博客
博客园 - 叶小钗
MongoDB | Blog
MongoDB | Blog
U
Unit 42
T
Tailwind CSS Blog
GbyAI
GbyAI
T
The Blog of Author Tim Ferriss
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
H
Hackread – Cybersecurity News, Data Breaches, AI and More
酷 壳 – CoolShell
酷 壳 – CoolShell
Google DeepMind News
Google DeepMind News
H
Help Net Security
Hugging Face - Blog
Hugging Face - Blog
爱范儿
爱范儿
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
N
Netflix TechBlog - Medium
B
Blog RSS Feed
大猫的无限游戏
大猫的无限游戏
aimingoo的专栏
aimingoo的专栏
A
About on SuperTechFans
Y
Y Combinator Blog
罗磊的独立博客
D
DataBreaches.Net
有赞技术团队
有赞技术团队
MyScale Blog
MyScale Blog
博客园_首页
博客园 - 三生石上(FineUI控件)
G
Google Developers Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
雷峰网
雷峰网
博客园 - 【当耐特】
Engineering at Meta
Engineering at Meta
博客园 - Franky
M
MIT News - Artificial intelligence
B
Blog
The Cloudflare Blog
Apple Machine Learning Research
Apple Machine Learning Research
I
InfoQ
S
SegmentFault 最新的问题
F
Fortinet All Blogs
阮一峰的网络日志
阮一峰的网络日志
Stack Overflow Blog
Stack Overflow Blog
Microsoft Security Blog
Microsoft Security Blog

Sanity.io

A Board Game agent built using Sanity Context and Vercel's AI SDK | Sanity Build a prototype with Claude Code that your whole team can edit | Sanity What’s New - May 2026 | Sanity I built a London pub guide with v0 and the Sanity MCP in six hours. Here's what I learned. | Sanity Build a conference concierge with Agent Context and Anthropic | Sanity Build a content-aware Telegram agent with Vercel AI SDK and Chat SDK | Sanity How I used Agent API to generate photos for my family’s recipes | Sanity What’s New April - 2026 | Sanity Better context, better matches: An AI love story (for dogs) | Sanity How to write for an agent | Sanity Content Agent, meet Slack: AI content operations in your workflow | Sanity Structure powers intelligence | Sanity Your agent needs better content. Here's how to give it. | Sanity How to serve content to agents (a field guide) | Sanity Sanity TypeGen GA: Automatic TypeScript types for content and GROQ | Sanity Sanity is now available on the Vercel Marketplace | Sanity The logo soup problem (and how to solve it) | Sanity Content Releases: From scattered updates to coordinated publishing | Sanity What's New - February 2026 | Sanity How we solved the agent memory problem | Sanity v0 Builder Challenge: The winners | Sanity Introducing: Sanity Agent Skills | Sanity Content Agent: Days of work in one conversation | Sanity Our Sanity Values | Sanity Open Source Pledge 2025: Stepping up when it matters | Sanity v0 builder challenge: $3000 in prizes | Sanity Why AI Breaks Without Structured Content Operations | Sanity What’s New January - 2026 | Sanity BFCM 2025: What teams built when infrastructure stopped being the problem | Sanity How AI shaped holiday shopping and what it means for content in 2026 | Sanity Sanity Studio v5: Embracing React 19 | Sanity You’ll need a CMS eventually. Let your agent set it up. | Sanity “You should never build a CMS” | Sanity AI Content Operations: A 30-Day Implementation Guide | Sanity What’s New December - 2025 | Sanity Scheduled Drafts: Stop manually publishing content at midnight | Sanity What’s New November - 2025 | Sanity Everything *[NYC] 2025 recap: A day of AI, Content Operations, and Culture | Sanity Clankers and content operations | Sanity Content Agent: AI that understands your structured content is here | Sanity Why design-driven content modeling creates technical debt, not velocity | Sanity What's New October - 2025 | Sanity From studio to inbox: How Kevin Green eliminated email campaign friction | Sanity The content editor's guide to content operations [E-commerce edition] | Sanity styled-components maintenance mode: A 40% faster fork | Sanity From zero code to a live website in 7 hours (thanks, Cursor!) | Sanity First attempt will be 95% garbage: A staff engineer's 6-week journey with Claude Code | Sanity Internationalization is more than translating words | Sanity What's New - September 2025 | Sanity We just deleted our 35k-member community Slack | Sanity What's New - August 2025 | Sanity The engineer's guide to content operations [E-commerce edition] | Sanity SEO for AI: Evolving from Web Pages to the Content Lake | Sanity What's New - July 2025 | Sanity Sanity Studio v4: A major version bump for a minor reason | Sanity What's New - June 2025 | Sanity Dashboard and Insights: Your New Content HQ | Sanity Canvas: AI-accelerated, context-aware, freeform authoring | Sanity Agent Actions: AI building blocks for structured content | Sanity Functions: Life beyond pressing publish | Sanity A new era for content applications with Sanity App SDK | Sanity The end of CMS era and our $85M Series C. | Sanity What's New – May 2025 | Sanity Introducing the Sanity Model Context Protocol (MCP) server | Sanity What's New – April 2025 | Sanity Pushing all the envelopes with ambitious content | Sanity Self-hosting is only free if your time is worth nothing | Sanity Content that lasts: Scaling beyond your frontend | Sanity The Live Content API is now Generally Available | Sanity The future beyond AI chat bots | Sanity Learning the new skill of working with AI | Sanity What's New - March 2025 | Sanity Give it in plain text: Making your content AI-Ready | Sanity No More 'DO NOT PUBLISH': Introducing Content Releases | Sanity React in 2025, what's next? | Sanity The final boss of front-end: block editors | Sanity Introducing Sanity for Startups | Sanity A block content editor that loves you back | Sanity A Black Friday Snooze Fest: Massive Traffic, No Drama | Sanity How to make a recipe site that scales well | Sanity The Sanity Winter Release 2024 | Sanity AVIF Arrives, Sanity’s Promise Fulfilled | Sanity Sanity joins the Open Source Pledge | Sanity Your content is now Live by default | Sanity Begin Team to Join Sanity | Sanity Sanity Digest - September '24 Edition | Sanity Sanity partners with Google. Now live on the Google Cloud Marketplace. | Sanity Sanity Digest - August ‘24 Edition | Sanity Now playing: the latest Mux Video Input plugin for Sanity | Sanity Community Digest - June ‘24 Edition | Sanity Community Digest - May ‘24 Edition | Sanity Guide to Sanity's newest product announcements | Sanity AI and Content Creation: A Leader's Guide | Sanity Of course, you should be able to type your content quickly! | Sanity New to AI Assist: translation, reference suggestions, image generation | Sanity Speak the language of your editors: Sanity Studio UI localization | Sanity Introducing the new Sanity Growth plan to serve collaborative teams | Sanity Presentation: Work faster than ever with structured content | Sanity Goodbye Feedback Frenzy, Hello Sanity Studio Comments! | Sanity Easing into the App Router with the Sanity Toolkit for Next.js | Sanity
Lab Notes: Setting up air quality notifications with Purple Air, Sanity, Vercel, and Twilio | Sanity
Even Westvang · 2020-09-02 · via Sanity.io

Haha. So guess where we live:

A map of US mainland showing Purple Air AQI measurements of US air
The Purple Air Sensor Network

Yes, in the Bay Area - out there on the left coast, by the red circles. In the COVID-19 pandemic in the mega-fire. And we’d set our eldest daughter up to hang out with her friends under some parasols on their Chromebooks. Just to see other kids and to pretend things are normal.

But we keep needing to cancel. Because there’s a mega-fire. In a global pandemic. We keep laughing about this. With a slightly hysterical edge, "Haha, the pandemic mega-fire, haha."

So we spend a lot of time looking at air quality indications – Purple Air specifically. Purple Air is a crowdsourced network that uses cheap sensors to measure particulates. And on Friday SO said she would like a text when it looks like the fires might force us all to stay in.

I can't do much about the fire or the pandemic, but this I can actually address over the weekend.

Get the source on GitHub

Deciding on a setup

So how does one build such a thing? What's the absolute easiest way? I don't write a lot of code these days. To give you an idea, this is me last night trying to remember how to get array length in JavaScript:

So we're going to need three things.

  1. A database – let's use a Sanity.io project with an instant dashboard for our distribution list, Purple Air sensor IDs, measurements and broadcasts
  2. Notifications – Let's use Twilio. Guess we could do email or app notifications, but SMS has a nice emergency services vibe that seems good for this
  3. A little bit of compute – just a few cycles to read and write data between endpoints

Was wondering about where to get the compute from. Five years ago I might have run this as a droplet on Digital Ocean. Or on a tiny instance on GCP or AWS. I would need to persist measurements in a database so maybe run SQLLite or even a full Postgres.

Believe it or not, I first tried to use cloud functions on GCP. My reasoning: all the data science people are pythonistas. A quick google and there are libs for converting and getting at all kinds of air quality data. I waste 45 minutes trying to set up python right. In encountered at least 75% of this chart:

XKCD of completely confusing python setup
Me, on a Friday eve, setting up Python. Source: https://xkcd.com/1987/

So for compute I instead go for JavaScript on Vercel. I'd rather take the hit of porting libraries to JavaScript to get started right away.

Initial setup: Sanity and Vercel (15min)

sanity init takes 2 (two) minutes. Remember to make your datasets private as we're storing phone numbers and they're semi-personal.

Vercel is just as fast. Started with the web onboard, selected a next.js sample as it says it comes with a serverless function. And it's up and deployed and running on local. Very slick given the huge amounts of magic involved.

We also need an API key also from manage.sanity.io so I can read/write to sanity.

I set up my environment variables in Vercel, but don't read the fine print and mistakenly assume the envs would be injected by vercel dev, but no. You need to vercel env pull.

I now have not only a cloud function, and also environment handling on local and in production, that’s nice!

I also have a nice React/Next.js website that I don’t really need, but that’s also working.

Let's also set up our Sanity schemas for the phone numbers of people to message and the sensor ID for the Purple Air sensors we want to listen to:

That’s nice. Newly updated version of Sanity Studio lookin’ all Chef’s Kiss here:

The studio showing sensor id

Getting at Air Quality Data (1 hour - long, fun detour)

Let’s get the data. I am so grateful for async await syntax. Asynchronous JavaScript programming feels like regular expressions to me. It takes an hour to install, at least, if you haven't been around it for a while.

YOLO, no error handling.

The raw data looks like this:

That’s three different pm2_5 values. A little googling tells us it's pm2_5_atm we want as it’s corrected for outdoor atmospherics.

Yeah, and there’s no air quality number (AQI) here! But that's what everyone wants to know.

So how do we calculate an AQI. AQI is based on biology – it’s how much the air is hurting you at any given time. Don't think the air can hurt you? Think again.

Turns out the EPA has 200-page tomes on background, but it’s really just a little table mapping PM 2.5 breakpoints value ranges to AQI ranges. Within each range you interpolate.

PM25 to AQI conversion index
PM25 to AQI conversion index

Someone wrapped this up nicely in Typescript for us!

So to get from IoT sensors to AQI we’re looking at:

This value is wrong though. It's way too high. Turns out cheap sensors get the value wrong. Especially for wood smoke. The Lane Regional Air Protection Agency (LRAPA) bought a couple of Purple Air sensors and put them next to their expensive Scientific Instruments™ and came back with a report with this figure:

A grid of values from an experiment
Figure from the LRAPA report on purple air sensors

So uh, the sensors align on offset on the Y-axis (intercept), they have approximately the same curve (R2), but the Slope is clearly different. Actually, the expensive instruments are 0.5 of the Purple Air sensors.

I wish the PDF could have had as a title: You, yes you, divide by two for wood smoke!

Wondering how the sensor actually works? There's an appendix at the end!

Sending SMSes

Let’s sign up for Twilio. There are some restrictions on what you can do without a verified account so I throw $20 at them to get a phone number. After that it more or less Just Works™.

I now have a through-line from the data store -> purple air -> AQI LRAPA (or LARPa as I like to think of it) -> my phone! Yatta!

White text on black background showing values in the 40s ranges and the ffufufufu expletive
Texts containing AQI measurements sent by SMS


And I still haven't plugged in my laptop to charge it. Two hours have gone by. Still to do:

  • Register sent messages so we can throttle
  • Check if we’re trending one way or another and have crossed a threshold (AQI 75 & 100?)
  • Generate a message and send to everyone
  • Maybe add an image? Chart? To send in an MMS?

Telling “Fine” from “This is Fine”

Ok, so let’s figure out a simple boundary crossing detection from “fine” to “this is fine”.

We’re currently just storing users and sensors with Sanity. Let’s go ahead and use it for measurements and broadcasts as well. I don’t know if sending messages to myself and a couple of our friends is a "broadcast", but at least it sounds cool.

Schema:

Let's do some reading:

And some writing:

Sweet!

Sanity Studio showing a measurement written over the API
Sanity Studio showing a measurement written over the API

We now need to send messages to people when stuff changes. Let’s make some AQI bands:

You really want pretty robust qualifications of what an “event” is when you’re messaging people. If a value hovers around a boundary you could end up sending people messages every 5 minutes. That would be super annoying.

As “Simple Methods for Detecting Zero Crossing” (2003) puts it:

Affects of noise, harmonics, and multi-frequency signal make frequency and period measurements difficult for synchronizing control events. Various methods are presented to minimize errors in period and phase measurements. Both frequency and amplitude domain approaches are analyzed. Post detection processing allows greater accuracy. Static and dynamic hystereses as well as interpolation methods of zero-crossing detection are investigated.

OTOH, life is short. Let’s go with:

  • Send a message when we go between boundaries
  • Don’t send people messages more than every 5 minutes
  • Don’t send people a message if the absolute AQI value has changed less than 10 since last message broadcast

We also have our “person” objects in Sanity so we could add messaging prefs that you could configure over SMS with Twilio through a little callback. Let’s not do that now. Maybe never.

With the message:

CRON in the cloud

I need to run my little cloud function on a timetable. Every minute I want it called. There’s CRON as a service, but I don’t know if I need a new service to do curl every few minutes. I futz around with GitHub actions for a good 30 minutes but spinning up a container to do a single HTTPS GET feels like going shopping in a Sherman tank.

Also: 5 minutes is the fastest GitHub will spin it. And I want minute intervals. GitHub action DX is surprisingly hard. Docs are “top-down abstracts first” and 30 minutes just understanding what goes where.

I so wish Vercel had CRON.

CRON as a service it is then. Easycron.com has won the SEO wars and looks fine. It’s feature-complete af, which isn’t strange given this is all they do:


I picture a world where the humans are gone, but where solar-powered CRON HTTP GET keeps doing its thing. Senselessly sending air quality warnings to non-existent endpoints.

Charts?!

Twilio sends images as well if you just append an URL to an image. So let’s use Quickchart, which is really just chart.js sitting in the cloud rendering PNGs:

End Result

Photo of an iPhone showing air quality index as graph
WFH AQI BBQ

The sum total for the server side is 158 lines of pedestrian JS:

The schema for the Sanity.io Studio another 76:

So, here's to this never actually sending a single message to anyone so the kids can hang out in the backyard.

Things TODO still:

  • Catch errors
  • Drop outliers in time series
  • Be able to add a bunch of local sensors, drop outliers and average out

Appendix A: So how do you measure particle concentrations so cheaply, anyway

Inside the Purple Air sensors, you find the PMS5003 sensor made by Plantower. According to their marketing it's exactly the same size as a uh, Zippo lighter:

Double Lung Electronics (!) will happily sell you them on Ali Express for $14 dollars apiece.

A teardown (and cleaning) shows that there are almost no moving parts. They're super simple and therefore very cheap.

So how do you measure both particulate density and size with a single photodiode and a single 658nm laser? Turns out there’s a lot going on when you have particles that are just a bit bigger than the wavelength of light. The way the particles spread light goes from Rayleigh to Mie and there are sinusoidal nonlinearities in the backscatter that you apparently can use to detect particle size:

This property of the scattering lets you make really cheap sensors to detect smoke particles from a mega-fire in a pandemic.