介绍
本次购买的RS485信号小型超声波一体式气象站由山东塞恩电子科技有限公司制造。
设备型号:SN-3003-FSXCS-N01
包含气温,湿度,大气压强,风速,风向,光学雨量共6个气象要素。供电方面支持10V-30V DC宽电压,可配合DC12v-TypeC与ESP32一同供电。
关注几个主要参数:大气压强测量精度只有1hPa,最大误差±1.5hPa,这一点对于测量天气系统已经基本死刑。气温测量精度0.1℃,最大误差±0.5℃
以及,虽然它的气温探头在百叶箱中,但是形同虚设。置于室外环境中,气温波动猛烈,在太阳直照仪器时可探测到40℃高温。
作为超声波测量仪器,其风速精度较高,误差优于国家标准站的标准,最大误差仅±(0.5+0.02V)m/s(0.5m/s启动风速)。也正由于测量原理,采样方式与风杯机械结构不同,易导致采样区间内的极值被平滑低估?
传输方式
RS485信号转TTL信号连接到ESP32,进入loop发送问询并读取应答数据,通过GPRS/WiFi传输原始6要素数据到中转服务器,数据换算(海平面气压,露点,分钟级降水)、入库。
使用的ESP32需要从NTP获取网络时间或者使用GNSS授时。
数据帧结构
地址码:为变送器的地址,在通讯网络中是唯一的(出厂默认0x01)
功能码:主机所发指令功能指示
数据区:数据区是具体通讯数据,16bits数据高字节
CRC码:二字节的校验码
主机问询帧结构
| 地址码 | 功能码 | 寄存器起始地址 | 寄存器长度 | 校验码低字节 | 校验码高字节 |
|---|---|---|---|---|---|
| 1字节 | 1字节 | 2字节 | 2字节 | 1字节 | 1字节 |
从机应答帧结构
| 地址码 | 功能码 | 有效字节数 | 数据一区 | 数据二区 | 数据N区 | 校验码低字节 | 校验码高字节 |
|---|---|---|---|---|---|---|---|
| 1字节 | 1字节 | 1字节 | 2字节 | 2字节 | 2字节 | 1字节 | 1字节 |
寄存器地址
| 寄存器地址 | PLC或组态地址 | 内容 | 支持功能码 | 定义说明 |
|---|---|---|---|---|
| 500 | 40501 | 风速值 | 0x03/0x04 | 实际值的100倍 |
| 501 | 40502 | 风力 | 0x03/0x04 | 实际值(当前风速对应的风级值) |
| 502 | 40503 | 风向(0-7档) | 0x03/0x04 | 实际值(正北方向为0,顺时针增加数值,正东方为2) |
| 503 | 40504 | 风向(0-360°) | 0x03/0x04 | 实际值(正北方向为0°顺时针增加度数,正东方为90°) |
| 504 | 40505 | 湿度值 | 0x03/0x04 | 实际值的10倍 |
| 505 | 40506 | 温度值 | 0x03/0x04 | 实际值的10倍 |
| 509 | 40510 | 大气压值(单位kPa) | 0x03/0x04 | 实际值的10倍 |
| 513 | 40514 | 光学雨量雨量值(单位:mm) | 0x03/0x04 | 实际值的10倍 |
| 寄存器地址 | 内容 | 支持功能码 | 定义说明 |
|---|---|---|---|
| 6000 H | 小型超声波风向偏移寄存器 | 0x06 | 0代表正常方向 1代表方向偏移180° |
| 6001 H | 小型超声波风速调零寄存器 | 0x06 | 写入0xAA,等待10s后,设备调零 |
| 6002 H | 光学雨量调零寄存器 | 0x06 | 写入0x5A,雨量值调零 |
| 6003 H | 光学雨量灵敏值 | 0x06 | 默认为11 H,改小后可增加雨量灵敏度 |
构造问询帧
const uint8_t REQUEST_FRAME[][8] = {
{0x01, 0x03, 0x01, 0xF8, 0x00, 0x02, 0x44, 0x06}, // 温湿度
{0x01, 0x03, 0x01, 0xF4, 0x00, 0x01, 0xC4, 0x04}, // 风速
{0x01, 0x03, 0x01, 0xFD, 0x00, 0x01, 0x14, 0x06}, // 大气压强
{0x01, 0x03, 0x02, 0x01, 0x00, 0x01, 0xD4, 0x72}, // 总降雨量(自通电后)
{0x01, 0x06, 0x60, 0x02, 0x00, 0x5A, 0xB6, 0x31} // 清除降雨
}
void ReadT(HardwareSerial &serial, float &temperature, float &humidity) {
serial.eventQueueReset();
serial.write(REQUEST_FRAME[0], sizeof(REQUEST_FRAME[0]));
delay(500);
if (serial.available())
{
uint8_t buffer[128];
int len = serial.readBytes(buffer, sizeof(buffer));
if (len > 0 && len == 9 && buffer[0] == 0x01 && buffer[1] == 0x03)
{
int t = buffer[5] * 256 + buffer[6];
temperature = t / 10.0;
int rh = buffer[3] * 256 + buffer[4];
humidity = rh / 10.0;
}
}
}
#include <WiFi.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#include <HTTPClient.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
#include <ArduinoJson.h>
#include <math.h>
const char* SSID = "TQM_DORMITORY";
const char* PASSWORD = "TQM_DORMITORY";
const char* API_URL = "https://SDKAPI/api/receive_meteo_data_json";
const uint8_t WIND_SPEED_QUERY[] = {0x02, 0x03, 0x00, 0x00, 0x00, 0x01, 0x84, 0x39};
class Module {
public:
Module() : timeClient(ntpUDP, "pool.ntp.org", 0, 60000) {}
void begin() {
Serial.begin(115200);
Serial2.begin(9600, SERIAL_8N1, 16, 17);
connectWiFi();
initBME280();
timeClient.begin();
}
void run() {
timeClient.update();
float temperature = bme280.readTemperature();
float humidity = bme280.readHumidity();
float pressure = bme280.readPressure() / 100;
float windSpeed = readWindSpeed();
float gps_h = 30;
String jsonPayload = initPayload(temperature, humidity, pressure, windSpeed, gps_h);
sendData(jsonPayload);
delay(10000);
}
private:
WiFiUDP ntpUDP;
NTPClient timeClient;
Adafruit_BME280 bme280;
void connectWiFi() {
Serial.print("Connecting to WiFi");
WiFi.begin(SSID, PASSWORD);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("Connected!");
}
void initBME280() {
if (!bme280.begin(0x76)) {
Serial.println("Sensor not found");
while (1) delay(10);
}
}
float readWindSpeed() {
Serial2.write(WIND_SPEED_QUERY, sizeof(WIND_SPEED_QUERY));
delay(1000);
if (Serial2.available()) {
uint8_t buffer[7];
int len = Serial2.readBytes(buffer, sizeof(buffer));
if (len == 7 && buffer[0] == 0x02 && buffer[1] == 0x03) {
int fs10 = buffer[3] * 256 + buffer[4];
return fs10 / 10.0;
}
}
Serial.println("Failed to read wind speed");
return -1;
}
String initPayload(float t, float rh, float p, float ws, float gps_h) {
StaticJsonDocument<256> jsonDoc;
jsonDoc["timestamp"] = timeClient.getEpochTime();
jsonDoc["instrument"] = "Station";
jsonDoc["observation"]["t"] = t;
jsonDoc["observation"]["rh"] = rh;
jsonDoc["observation"]["p"] = p;
jsonDoc["observation"]["dt"] = dewPointFast(t, rh);
jsonDoc["observation"]["mslp"] = mslp(p, t, gps_h);
jsonDoc["wind"]["spd"] = ws;
jsonDoc["wind"]["dir"] = 0;
jsonDoc["gps"]["lat"] = 0;
jsonDoc["gps"]["long"] = 0;
jsonDoc["gps"]["alt"] = gps_h;
jsonDoc["gps"]["dir"] = 0;
jsonDoc["gps"]["speed"] = 0;
String jsonString;
serializeJson(jsonDoc, jsonString);
return jsonString;
}
void sendData(const String& payload) {
if (WiFi.status() == WL_CONNECTED) {
HTTPClient http;
http.begin(API_URL);
http.addHeader("Content-Type", "application/json");
int responseCode = http.POST(payload);
http.end();
} else {
Serial.println("WiFi not connected");
}
}
float mslp(float p, float t, float h) {
return p * pow((1 - 0.0065 * h / (t + 0.0065 * h + 273.15)), -5.257);
}
float dewPointFast(float celsius, float humidity) {
float a = 17.271, b = 237.7;
float temp = (a * celsius) / (b + celsius) + log(humidity / 100.0);
return round((b * temp) / (a - temp) * 100) / 100.0;
}
};
Module collector;
void setup() {
collector.begin();
}
void loop() {
collector.run();
}
总结
总之就是很不推荐购买使用。







Comments | NOTHING