資料上傳 API 和範例腳本

第一步是從資料平台頁面取得令牌。

擁有自己的令牌後,您可以使用以下腳本上傳資料。上傳第一個網站資料後,請前往 aqicn.org/data-feed/verification/ 設定網站並驗證上傳的資料。

支援的軟體平台:

我們為這 3 個平台提供即用型軟體:

  • Arduino:如果您有 Arduino CPU,請使用 github.com 上提供的即用型軟體,網址為 aqicn/gaia-a08-arduino
  • Python:使用下面的程式碼片段
  • 命令列 (CURL):使用下面的程式碼片段

如果您沒有任何監測站,但想擁有一個,請查看我們的GAIA空氣品質監測站。

如果您更喜歡 DIY 電台,請查看 GAIA A08


--

範例程式碼(Python)

import requests  
 
# Sensor parameter   
sensorReadings = [   
	{'specie':'pm25', 'value': 393.3},  
	{'specie':'pm10', 'value': 109.3}  
] 
 
# Station parameter   
station = { 
	'id':		"station-001",  
	'location':  { 
		'latitude': 28.7501,  
		'longitude': 77.1177 
	} 
} 
 
# User parameter - get yours from https://aqicn.org/data-platform/token/ 
userToken = "dummy-token-for-test-purpose-only" 
 
# Then Upload the data  
params = {'station':station,'readings':sensorReadings,'token':userToken}  
request = requests.post( url = "https://aqicn.org/sensor/upload/",  json = params) 
#print(request.text) 
data = request.json()  
 
if data["status"]!="ok": 
	print("Something went wrong: %s" % data) 
else: 
	print("Data successfully posted: %s"%data) 

範例程式碼(捲曲)

curl -X POST https://aqicn.org/sensor/upload -H 'Content-Type: application/json' --data '{\ 
"token": "dummy-token-for-test-purpose-only",\ 
"station": { "id": "station-001" },\ 
"readings": [{"specie":"pm2.5", "value": 393.3}]\ 
}'

範例程式碼(arduino)

Check github.com/aqicn/gaia-a08-arduino for the full code.
#include <WiFi.h> 
#include <HTTPClient.h> 
#include <ArduinoJson.h> 
 
#define LATITUDE 28.7501 
#define LONGITUDE 77.1177 
 
void upload(float pm25_concentration, float pm10_concentration, const char * token) 
{ 
 
    static char stationID[32]; 
    uint64_t efuseMac = ESP.getEfuseMac(); 
    uint16_t chip = (uint16_t)(efuseMac >> 32); 
    snprintf(stationID, 32, "station-%x", chip); 
 
    doc["token"] = token; 
    doc["station"]["id"] = stationID; 
 
    doc["station"]["location"]["latitude"] = LATITUDE; 
    doc["station"]["location"]["longitude"] = LONGITUDE; 
 
    doc["readings"][0]["specie"] = "pm25"; 
    doc["readings"][0]["value"] = pm25_concentration; 
    doc["readings"][0]["unit"] = "µg/m3"; 
 
    doc["readings"][1]["specie"] = "pm10"; 
    doc["readings"][1]["value"] = pm10_concentration; 
    doc["readings"][1]["unit"] = "µg/m3"; 
 
    static char json_body[1024]; 
    serializeJson(doc, json_body); 
 
    HTTPClient http; 
    http.begin("https://aqicn.org/sensor/upload"); 
    http.addHeader("Content-Type", "application/json"); 
    int httpResponseCode = http.POST(json_body); 
 
    if (httpResponseCode > 0) 
    { 
 
        String response = http.getString(); 
        Serial.println(httpResponseCode); 
        Serial.println(response); 
    } 
    else 
    { 
 
        Serial.print("Error on sending POST: "); 
        Serial.println(httpResponseCode); 
    } 
 
    http.end(); 
}

API 選項

Parameter Type Optional/Mandatory Explanations
token string mandatory

aqicn.org/data-platform/token 取得您自己的令牌。

station
station.id string mandatory

唯一的電台 ID - 您可以選擇最多 128 個字元的任何名稱。
此名稱僅供您內部使用。其他人不會看到此 ID

station.name string optional

車站名稱 - 例如可以是您的建築物名稱、街道名稱、大學系名稱、您個人氣象站的代碼。
此名稱將用作您的電台 URL 的後綴。

station.latitude float optional

您所在車站的經度

station.longitude float optional

您所在車站的經度

organization
org.website string optional

如果您有一個網站,其中包含有關您的站/感測器的更多信息,當您使用查看您的站時,我們會在地圖上添加此鏈接

org.name string optional

如果您指定網站,則此「組織名稱」將與該網站關聯。

readings
readings[*].specie string mandatory

您報告的污染物名稱。對於氣體感測器,使用:“pm2.5”、“pm10”、“pm1.0”,... 對於氣體感測器,使用:“co2”、“no2”、“o3”,... 對於天氣感測器,使用:“溫度”,“濕度”,“壓力”,“風速”,“陣風”,“風向”,..
您實際上可以使用您想要的任何物種名稱。當您的電台經過驗證後,名稱將在我們的系統中標準化。

readings[*].value float mandatory

如果您的感測器每秒產生值,並且您每分鐘才上傳一次,則該值應該是過去一分鐘內讀取的所有值的平均值。

readings[*].unit string optional

值的單位。例如,「mg/m3」表示灰塵感測器,ppb 表示氣體感測器,C 表示溫度感測器。

readings[*].time string optional

ISO 8601 格式的讀數日期和時間

readings[*].min float optional

如果讀取值是基於多個值的平均值,則這對應於用於求平均值的所有值的最小值。

readings[*].max float optional

如果讀數值是基於多個值的平均值,則這對應於用於求平均值的所有值的最大值。

readings[*].median float optional

如果讀數值是基於多個值的平均值,則這對應於用於求平均值的所有值的中位數。

readings[*].stddev float optional

如果讀數值是基於多個值的平均值,則這對應於用於求平均值的所有值的標準差。

readings[*].averaging float optional

如果上述值是基於多個值的平均值,則這對應於平均週期的持續時間(以秒為單位)。
例如,使用 60 表示一分鐘的平均數據,使用 3600 表示每小時的平均數據。

實施例1

{ 
	"token": "......", 
	"station": { 
		"id": "station-001", 
		"name": "HCPA Santa Cecília", 
		"latitude": 103.37893, 
		"longitude": 43.17108, 
	}, 
	"org": { 
		"website":"https://pacto.upsensor.com/", 
		"name":"Porto Ar Alegre", 
	}, 
	"readings": [ 
		{"time":"2025-09-01T16:34:36+09:00","specie":"pm2.5", "value": 393.3, "unit":"mg/m3", "min":390.3, "max": 402.3, "stddev": 0.332},  
		{"time":"2025-09-01T16:34:36+09:00","specie":"pm10", "value": 109.3, "unit":"mg/m3"}, 
		{"time":"2025-09-01T16:34:36+09:00","specie":"co2", "value": 459.3, "unit":"ppb"}, 
		{"time":"2025-09-01T16:34:36+09:00","specie":"temp", "value": 26.8, "unit":"C"}, 
	] 
}

實施例2

{ 
	"token": "......", 
	"station": { 
		"id": "station-001", 
	}, 
	"readings": [ 
		{"specie":"pm2.5", "value": 393.3} 
	] 
}

完整程式碼範例

您可以使用此程式碼從 SDS 感測器連續讀取,並每分鐘上傳一次:(腳本也可從 https://github .com/aqicn/sds-sensor-reader)。

import requests 
import random 
import time 
import math 
import json 
import sys 
from serial import Serial 
 
LOCATION = {'latitude': 28.7501, 'longitude': 77.1177} 
TOKEN    = "dummy-token-for-test-purpose-only" 
SENSORID = "station-001" 
USBPORT  = "/dev/ttyUSB0" 
 
class SensorDataUploader: 
 
    def __init__(self, station, token): 
        self.token = token 
        self.station = station 
 
 
    def send(self,readings): 
 
        params = {'station':self.station,'readings':readings,'token':self.token}  
        print("Uploading: %s"%json.dumps(params, indent=4)) 
 
        request = requests.post( url = "https://aqicn.org/sensor/upload/",  json = params) 
        data = request.json()  
        if data["status"]!="ok": 
            print("Something went wrong: %s" % data) 
        else: 
            print("Data successfully posted: %s"%data) 
 
 
 
 
class Accumulator: 
 
    def __init__(self, name): 
        self.name = name 
        self.values = [] 
 
    def add(self,val): 
        self.values.append(val) 
 
    def count(self): 
        return len(self.values) 
 
    def reset(self): 
        self.values=[] 
 
    def min(self): 
        return self.values[0] 
 
    def max(self): 
        return self.values[len(self.values)-1] 
 
    def median(self): 
        return self.values[len(self.values)/2] 
 
    def mean(self): 
        return float(sum(self.values)) / len(self.values) 
 
    def stddev(self): 
        l = len(self.values) 
        mean = self.mean() 
        return math.sqrt(float(reduce(lambda x, y: x + y, map(lambda x: (x - mean) ** 2, self.values))) / l) 
 
 
    def summary(self): 
        self.values.sort() 
        return {"specie":self.name,'value':self.mean(),'min':self.min(),'max':self.max(),'median':self.median(), 'stddev':self.stddev()}  
 
 
 
class DummyReader: 
 
    def read( self ): 
 
        time.sleep(1.1) 
        return {"pm2.5":random.random()*10,"pm10":random.random()*10} 
 
 
class SDS011Reader: 
 
    def __init__(self, inport): 
        self.serial = Serial(port=inport,baudrate=9600) 
        self.values = [] 
        self.step = 0 
 
    def read( self ): 
 
        # time.sleep(1) 
        # return {"pm2.5":random.random()*100,"pm10":random.random()*100} 
 
        while self.serial.inWaiting()!=0: 
            v=ord(self.serial.read()) 
 
            if self.step ==0: 
                if v==170: 
                    self.step=1 
 
            elif self.step==1: 
                if v==192: 
                    self.values = [0,0,0,0,0,0,0] 
                    self.step=2 
                else: 
                    self.step=0 
 
            elif self.step>8: 
                self.step =0 
                pm25 = (self.values[0]+self.values[1]*256)/10 
                pm10 = (self.values[2]+self.values[3]*256)/10 
                return {"pm2.5":pm25,"pm10":pm10} 
 
            elif self.step>=2: 
                self.values[self.step-2]=v 
                self.step= self.step+1 
 
        return None 
 
 
 
def readAndUpload(sensor, uploader): 
 
    try: 
 
        while True: 
            accumulators = {} 
            startTime = time.time() 
 
            while time.time() < startTime+60: 
                values = sensor.read() 
                if values==None: 
                    continue 
 
                print("Reading [%2d]: %s"%(int(time.time()-startTime),values)) 
                for specie, value in values.items(): 
                    if not (specie in accumulators): 
                        accumulators[specie]=Accumulator(specie) 
                    accumulators[specie].add(value) 
 
 
            readings = [] 
            for specie, accumulator in accumulators.items(): 
                readings.append(accumulator.summary()) 
 
            if len(readings)>0: 
                uploader.send(readings) 
            else: 
                print("No value read from the sensor...") 
 
 
    except KeyboardInterrupt: 
        print "Bye" 
        sys.exit() 
 
 
 
print("Starting reading sensor "+SENSORID+" on port "+USBPORT) 
 
# Station parameter   
station = {'id':SENSORID, 'location':LOCATION} 
uploader = SensorDataUploader(station, TOKEN) 
 
sensor = SDS011Reader(USBPORT) 
# sensor = DummyReader() 
readAndUpload(sensor,uploader) 
 

您知道您所在地區有空氣品質監測站嗎?
為什麼不使用您自己的空氣品質站參與地圖呢?

我們的 GAIA 空氣品質監測儀設定非常簡單:您只需要一個 WIFI 存取點和一個 USB 相容電源。

連接後,您可以透過地圖和 API 立即獲得即時空氣污染等級。

該站配有 10 公尺長的防水電源線、USB 電源、安裝設備和可選的太陽能電池板。

關於空氣質量與空氣污染指數

本網站採用的污染指數和顏色與EPA是完全相同的。 EPA的指數可以從 AirNow上查到

空气质量指数 空气质量指数级别(状况)及表示颜色 对健康影响情况 建议采取的措施
0 - 50 一级(优) 空气质量令人满意,基本无空气污染 各类人群可正常活动
51 -100 二级(良) 空气质量可接受,但某些污染物可能对极少数异常敏感人群健康有较弱影响 极少数异常敏感人群应减少户外活动
101-150 三级(轻度污染) 易感人群症状有轻度加剧,健康人群出现刺激症状 儿童、老年人及心脏病、呼吸系统疾病患者应减少长时间、高强度的户外锻炼
151-200 四级(中度污染) 进一步加剧易感人群症状,可能对健康人群心脏、呼吸系统有影响 儿童、老年人及心脏病、呼吸系统疾病患者避免长时间、高强度的户外锻炼,一般人群适量减少户外运动
201-300 五级(重度污染) 心脏病和肺病患者症状显著加剧,运动耐受力降低,健康人群普遍出现症状 儿童、老年人及心脏病、肺病患者应停留在室内,停止户外运动,一般人群减少户外运动
300+ 六级(严重污染) 健康人群运动耐受力降低,有明显强烈症状,提前出现某些疾病 儿童、老年人和病人应停留在室内,避免体力消耗,一般人群避免户外活动
(参考详见 http://zh.wikipedia.org/wiki/空气质量指数)

如果你想了解更多有關空氣質量與污染,詳見維基百科或者 AirNow

有關健康建議詳​​見北京的Richard Saint Cyr MD醫生的博客:www.myhealthbeijing.com


注意事項: 所有空氣品質數據在發佈時均未經驗證,且為了確保資料準確性,這些數據可能隨時被修改,恕不另行通知。 世界空氣品質指數專案在編制這些訊息內容時已經謹慎的運用了所有適當的技巧,在任何情況下世界空氣品質指數 在任何情況下,世界空氣品質指數專案團隊或其代理人均不對因提供此數據而直接或間接引起的任何損失、傷害或損害來承擔契約、侵權或其他責任。



設定


選擇語言:


Temperature unit:
Celsius