This commit is contained in:
2025-06-17 17:24:26 +08:00
parent a8b18d4c28
commit b01d768d28
17 changed files with 1535 additions and 531 deletions

View File

@ -14,6 +14,7 @@ board = esp32-s3-devkitc-1
framework = arduino framework = arduino
lib_deps = lib_deps =
robtillaart/INA226@^0.6.0 robtillaart/INA226@^0.6.0
robtillaart/DS18B20@^0.2.4
esphome/ESPAsyncWebServer-esphome@^3.3.0 esphome/ESPAsyncWebServer-esphome@^3.3.0
bblanchon/ArduinoJson@^7.4.1
monitor_speed = 115200 monitor_speed = 115200

240
src/DHT11.cpp Normal file
View File

@ -0,0 +1,240 @@
/**
* DHT11.cpp
* Library for reading temperature and humidity from the DHT11 sensor.
*
* Author: Dhruba Saha
* Version: 2.1.0
* License: MIT
*/
#include "DHT11.h"
/**
* Constructor for the DHT11 class.
* Initializes the pin to be used for communication and sets it to output mode.
*
* @param pin: Digital pin number on the Arduino board to which the DHT11 sensor is connected.
*/
DHT11::DHT11(int pin) : _pin(pin)
{
pinMode(_pin, OUTPUT);
digitalWrite(_pin, HIGH);
}
/**
* Sets the delay between consecutive sensor readings.
* If this method is not called, a default delay of 500 milliseconds is used.
*
* @param delay: Delay duration in milliseconds between sensor readings.
*/
void DHT11::setDelay(unsigned long delay)
{
_delayMS = delay;
}
/**
* Reads raw data from the DHT11 sensor.
* This method handles the direct communication with the DHT11 sensor and retrieves the raw data.
* It's used internally by the readTemperature, readHumidity, and readTemperatureHumidity methods.
*
* @param data: An array of bytes where the raw sensor data will be stored.
* The array must be at least 5 bytes long, as the DHT11 sensor returns 5 bytes of data.
* @return: Returns 0 if the data is read successfully and the checksum matches.
* Returns DHT11::ERROR_TIMEOUT if the sensor does not respond or communication times out.
* Returns DHT11::ERROR_CHECKSUM if the data is read but the checksum does not match.
*/
int DHT11::readRawData(byte data[5])
{
delay(_delayMS);
startSignal();
unsigned long timeout_start = millis();
while (digitalRead(_pin) == HIGH)
{
if (millis() - timeout_start > TIMEOUT_DURATION)
{
return DHT11::ERROR_TIMEOUT;
}
}
if (digitalRead(_pin) == LOW)
{
delayMicroseconds(80);
if (digitalRead(_pin) == HIGH)
{
delayMicroseconds(80);
for (int i = 0; i < 5; i++)
{
data[i] = readByte();
if (data[i] == DHT11::ERROR_TIMEOUT)
{
return DHT11::ERROR_TIMEOUT;
}
}
if (data[4] == ((data[0] + data[1] + data[2] + data[3]) & 0xFF))
{
return 0; // Success
}
else
{
return DHT11::ERROR_CHECKSUM;
}
}
}
return DHT11::ERROR_TIMEOUT;
}
/**
* Reads a byte of data from the DHT11 sensor during the communication process.
*
* @return: A byte of data read from the sensor.
*/
byte DHT11::readByte()
{
byte value = 0;
for (int i = 0; i < 8; i++)
{
while (digitalRead(_pin) == LOW)
;
delayMicroseconds(30);
if (digitalRead(_pin) == HIGH)
{
value |= (1 << (7 - i));
}
while (digitalRead(_pin) == HIGH)
;
}
return value;
}
/**
* Sends a start signal to the DHT11 sensor to initiate a data read.
* This involves setting the data pin low for a specific duration, then high,
* and finally setting it to input mode to read the data.
*/
void DHT11::startSignal()
{
pinMode(_pin, OUTPUT);
digitalWrite(_pin, LOW);
delay(18);
digitalWrite(_pin, HIGH);
delayMicroseconds(40);
pinMode(_pin, INPUT);
}
/**
* Reads and returns the temperature from the DHT11 sensor.
* Utilizes the readRawData method to retrieve raw data from the sensor and then extracts
* the temperature from the data array.
*
* @return: Temperature value in Celsius. Returns DHT11::ERROR_TIMEOUT if reading times out,
* or DHT11::ERROR_CHECKSUM if checksum validation fails.
*/
int DHT11::readTemperature()
{
byte data[5];
int error = readRawData(data);
if (error != 0)
{
return error;
}
//return data[2];
int temp = data[2];
if (temp != DHT11::ERROR_CHECKSUM && temp != DHT11::ERROR_TIMEOUT)
{
return temp;
}
}
/**
* Reads and returns the humidity from the DHT11 sensor.
* Utilizes the readRawData method to retrieve raw data from the sensor and then extracts
* the humidity from the data array.
*
* @return: Humidity value in percentage. Returns DHT11::ERROR_TIMEOUT if reading times out,
* or DHT11::ERROR_CHECKSUM if checksum validation fails.
*/
int DHT11::readHumidity()
{
byte data[5];
int error = readRawData(data);
if (error != 0)
{
return error;
}
//return data[0];
int humi = data[0];
if (humi != DHT11::ERROR_CHECKSUM && humi != DHT11::ERROR_TIMEOUT)
{
return humi;
}
}
/**
* Reads and returns the temperature and humidity from the DHT11 sensor.
* Utilizes the readRawData method to retrieve raw data from the sensor and then extracts
* both temperature and humidity from the data array.
*
* @param temperature: Reference to a variable where the temperature value will be stored.
* @param humidity: Reference to a variable where the humidity value will be stored.
* @return: An integer representing the status of the read operation.
* Returns 0 if the reading is successful, DHT11::ERROR_TIMEOUT if a timeout occurs,
* or DHT11::ERROR_CHECKSUM if a checksum error occurs.
*/
int DHT11::readTemperatureHumidity(int &temperature, int &humidity)
{
byte data[5];
int error = readRawData(data);
if (error != 0)
{
return error;
}
humidity = data[0];
temperature = data[2];
return 0; // Indicate success
}
/**
* Returns a human-readable error message based on the provided error code.
* This method facilitates easier debugging and user feedback by translating
* numeric error codes into descriptive strings.
*
* @param errorCode The error code for which the description is required.
* @return A descriptive string explaining the error.
*/
String DHT11::getErrorString(int errorCode)
{
switch (errorCode)
{
case DHT11::ERROR_TIMEOUT:
return "Error 253 Reading from DHT11 timed out.";
case DHT11::ERROR_CHECKSUM:
return "Error 254 Checksum mismatch while reading from DHT11.";
default:
return "Error Unknown.";
}
}
int DHT11::getTempHumi(int &temperature, int &humidity)
{
// Attempt to read the temperature and humidity values from the DHT11 sensor.
int result = readTemperatureHumidity(temperature, humidity);
// Check the results of the readings.
// If the reading is successful, print the temperature and humidity values.
// If there are errors, print the appropriate error messages.
if (result == 0) {
Serial.print("Temperature: ");
Serial.print(temperature);
Serial.print(" °C\tHumidity: ");
Serial.print(humidity);
Serial.println(" %");
} else {
// Print error message based on the error code.
Serial.println(DHT11::getErrorString(result));
}
}

110
src/DHT11.h Normal file
View File

@ -0,0 +1,110 @@
/**
* DHT11.h
* Header file for the DHT11 library, providing functionalities to interface with
* the DHT11 temperature & humidity sensor.
*
* Author: Dhruba Saha
* Version: 2.1.0
* License: MIT
*/
#ifndef DHT11_h
#define DHT11_h
#include "Arduino.h"
/**
* DHT11 Class
* Provides methods to read temperature and humidity data from the DHT11 sensor.
*/
class DHT11
{
public:
/**
* Constructor
* Initializes the data pin to be used for communication with the DHT11 sensor.
*
* @param pin: Digital pin number on the Arduino board to which the DHT11 sensor is connected.
*/
DHT11(int pin);
/**
* Sets the delay between consecutive sensor readings.
* If this method is not called, a default delay of 500 milliseconds is used.
*
* @param delay: Delay duration in milliseconds between sensor readings.
*/
void setDelay(unsigned long delay);
/**
* Reads and returns the humidity from the DHT11 sensor.
*
* @return: Humidity value in percentage. Returns DHT11_ERROR_TIMEOUT if reading times out.
* Returns DHT11_ERROR_CHECKSUM if checksum validation fails.
*/
int readHumidity();
/**
* Reads and returns the temperature from the DHT11 sensor.
*
* @return: Temperature value in Celsius. Returns DHT11_ERROR_TIMEOUT if reading times out.
* Returns DHT11_ERROR_CHECKSUM if checksum validation fails.
*/
int readTemperature();
/**
* Reads and returns the temperature and humidity from the DHT11 sensor.
*
* @param temperature: Reference to a variable where the temperature value will be stored.
* @param humidity: Reference to a variable where the humidity value will be stored.
* @return: true if the reading is successful, false if it fails due to timeout or checksum error.
*/
int readTemperatureHumidity(int &temperature, int &humidity);
int getTempHumi(int &temperature, int &humidity);
// Constants to represent error codes.
static const int ERROR_CHECKSUM = 254; // Error code indicating checksum mismatch.
static const int ERROR_TIMEOUT = 253; // Error code indicating a timeout occurred during reading.
static const int TIMEOUT_DURATION = 1000; // Duration (in milliseconds) to wait before timing out.
/**
* Returns a human-readable error message based on the provided error code.
*
* @param errorCode: The error code for which the message is required.
* @return: A string describing the error.
*/
static String getErrorString(int errorCode);
private:
int _pin; // Pin number used for communication with the DHT11 sensor.
unsigned long _delayMS = 500; // Default delay in milliseconds between sensor readings.
/**
* Private method to read raw data from the DHT11 sensor.
* This method encapsulates the communication with the sensor and data reading process,
* and is utilized by public methods to get temperature and humidity data.
*
* @param data: Array to store the raw data read from the sensor.
* @return: An integer representing the status of the read operation.
* Returns 0 if the reading is successful, DHT11::ERROR_TIMEOUT if a timeout occurs,
* or DHT11::ERROR_CHECKSUM if a checksum error occurs.
*/
int readRawData(byte data[5]);
/**
* Reads a byte of data from the DHT11 sensor.
*
* @return: A byte of data read from the sensor.
*/
byte readByte();
/**
* Sends a start signal to the DHT11 sensor to initiate a data read.
* This involves setting the data pin low for a specific duration, then high,
* and finally setting it to input mode to read the data.
*/
void startSignal();
};
#endif

View File

@ -1,234 +0,0 @@
//
// FILE: DS18B20.cpp
// AUTHOR: Rob.Tillaart
// VERSION: 0.2.4
// DATE: 2017-07-25
// PURPOSE: library for DS18B20 temperature sensor with minimal footprint
// URL: https://github.com/RobTillaart/DS18B20_RT
// https://github.com/RobTillaart/DS18B20_INT
#include "DS18B20.h"
// OneWire commands
#define STARTCONVO 0x44
#define READSCRATCH 0xBE
#define WRITESCRATCH 0x4E
// Scratchpad locations
#define TEMP_LSB 0
#define TEMP_MSB 1
#define HIGH_ALARM_TEMP 2
#define LOW_ALARM_TEMP 3
#define CONFIGURATION 4
#define INTERNAL_BYTE 5
#define COUNT_REMAIN 6
#define COUNT_PER_C 7
#define SCRATCHPAD_CRC 8
// Device resolution
#define TEMP_9_BIT 0x1F // 9 bit
#define TEMP_10_BIT 0x3F // 10 bit
#define TEMP_11_BIT 0x5F // 11 bit
#define TEMP_12_BIT 0x7F // 12 bit
DS18B20::DS18B20(OneWire* ow, uint8_t resolution)
{
_oneWire = ow;
_addressFound = false;
_resolution = resolution;
_config = DS18B20_CLEAR;
_offset = 0;
}
bool DS18B20::begin(uint8_t retries)
{
_config = DS18B20_CLEAR;
if (isConnected(retries))
{
_setResolution();
}
return _addressFound;
}
bool DS18B20::isConnected(uint8_t retries)
{
_addressFound = false;
for (uint8_t rtr = retries; (rtr > 0) && (_addressFound == false); rtr--)
{
_oneWire->reset();
_oneWire->reset_search();
_deviceAddress[0] = 0x00;
_oneWire->search(_deviceAddress);
_addressFound = (_deviceAddress[0] != 0x00) &&
(_oneWire->crc8(_deviceAddress, 7) == _deviceAddress[7]);
}
return _addressFound;
}
void DS18B20::requestTemperatures(void)
{
_oneWire->reset();
_oneWire->skip();
_oneWire->write(STARTCONVO, 0);
}
bool DS18B20::isConversionComplete(void)
{
return (_oneWire->read_bit() == 1);
}
float DS18B20::getTempC(bool checkConnect)
{
ScratchPad scratchPad;
if (checkConnect)
{
if (isConnected(3) == false)
{
return DEVICE_DISCONNECTED;
}
}
if (_config & DS18B20_CRC)
{
readScratchPad(scratchPad, 9);
if (_oneWire->crc8(scratchPad, 8) != scratchPad[SCRATCHPAD_CRC])
{
return DEVICE_CRC_ERROR;
}
}
else
{
readScratchPad(scratchPad, 2);
}
int16_t rawTemperature = (((int16_t)scratchPad[TEMP_MSB]) << 8) | scratchPad[TEMP_LSB];
float temp = 0.0625 * rawTemperature;
if (temp < -55)
{
return DEVICE_DISCONNECTED;
}
if (_offset != 0)
{
temp += _offset;
}
return temp;
}
void DS18B20::setOffset(float offset)
{
_offset = offset;
}
float DS18B20::getOffset()
{
return _offset;
}
bool DS18B20::getAddress(uint8_t* buf)
{
if (_addressFound)
{
for (uint8_t i = 0; i < 8; i++)
{
buf[i] = _deviceAddress[i];
}
}
return _addressFound;
}
bool DS18B20::setResolution(uint8_t resolution)
{
if (isConnected())
{
_resolution = resolution;
_setResolution();
}
return _addressFound;
}
uint8_t DS18B20::getResolution()
{
return _resolution;
}
void DS18B20::setConfig(uint8_t config)
{
_config = config;
}
uint8_t DS18B20::getConfig()
{
return _config;
}
//////////////////////////////////////////////////
//
// PRIVATE
//
void DS18B20::readScratchPad(uint8_t *scratchPad, uint8_t fields)
{
_oneWire->reset();
_oneWire->select(_deviceAddress);
_oneWire->write(READSCRATCH);
for (uint8_t i = 0; i < fields; i++)
{
scratchPad[i] = _oneWire->read();
}
_oneWire->reset();
}
void DS18B20::_setResolution()
{
uint8_t res;
switch (_resolution)
{
case 12: res = TEMP_12_BIT; break;
case 11: res = TEMP_11_BIT; break;
case 10: res = TEMP_10_BIT; break;
// lowest as default as we do only integer math.
default: res = TEMP_9_BIT; break;
}
_oneWire->reset();
_oneWire->select(_deviceAddress);
_oneWire->write(WRITESCRATCH);
// two dummy values for LOW & HIGH ALARM
_oneWire->write(0);
_oneWire->write(100);
_oneWire->write(res);
_oneWire->reset();
}
void DS18B20::printTemperature()
{
requestTemperatures(); // 发送请求以开始温度转换
//float temperature = getTempC(); // 获取温度(摄氏度)
Serial.printf("Temp: %.2f\n", getTempC());
//Serial.print(getTempC());
//Serial.println("℃ ");
}
// -- END OF FILE --

View File

@ -1,66 +0,0 @@
#ifndef DS18B20_H
#define DS18B20_H
#include "OneWire.h"
#define DS18B20_LIB_VERSION (F("0.2.4"))
// Error Code
#define DEVICE_DISCONNECTED -127
#define DEVICE_CRC_ERROR -128
// configuration codes
#define DS18B20_CLEAR 0x00
#define DS18B20_CRC 0x01
typedef uint8_t DeviceAddress[8];
typedef uint8_t ScratchPad[9];
class DS18B20
{
public:
void printTemperature();
explicit DS18B20(OneWire * ow, uint8_t resolution = 9);
bool begin(uint8_t retries = 3);
bool isConnected(uint8_t retries = 3);
void requestTemperatures(void);
bool isConversionComplete(void);
// backwards compatible
float getTempC(bool checkConnect = true);
// conversion wrapper Fahrenheit
// (keep in .h for footprint)
float getTempF() { return 32.0 + getTempC() * 1.8; };
void setOffset(float offset = 0);
float getOffset();
bool getAddress(uint8_t * buf);
bool setResolution(uint8_t resolution = 9);
uint8_t getResolution(); // returns cached value
void setConfig(uint8_t config);
uint8_t getConfig();
private:
void readScratchPad(uint8_t *, uint8_t);
void _setResolution();
DeviceAddress _deviceAddress;
OneWire* _oneWire;
bool _addressFound;
uint8_t _resolution;
uint8_t _config;
float _offset;
};
#endif // DS18B20_H
// -- END OF FILE --

View File

@ -29,7 +29,9 @@ uint8_t DS3502::getWiper() {
* 设置新的 Wiper 值,范围为 0 到 127 * 设置新的 Wiper 值,范围为 0 到 127
*/ */
void DS3502::setWiper(uint8_t new_wiper_value) { void DS3502::setWiper(uint8_t new_wiper_value) {
//if (new_wiper_value > 127) return; // 超出范围则不进行操作 if (new_wiper_value > 99)
new_wiper_value = 100;
//return; // 超出范围则不进行操作
_wire->beginTransmission(_address); _wire->beginTransmission(_address);
_wire->write(DS3502_WIPER); // 指定写入 Wiper 寄存器 _wire->write(DS3502_WIPER); // 指定写入 Wiper 寄存器

View File

@ -14,22 +14,48 @@ float LightControl::getCurrentLight() {
return lightSensor.readLightLevel(); return lightSensor.readLightLevel();
} }
void LightControl::adjustWiper() { void LightControl::runUntilTargetReached(float target, int maxAttempts) {
float currentLight = getCurrentLight(); const float tolerance = 100.0f; // 容许误差范围±100 lux
float error = targetLightLevel - currentLight; float currentLight;
float res;
// 计算输出变化量(比例控制) int attempt = 0;
int delta = (int)(abs(error) * Kp); const int max_retries = maxAttempts; // 最大尝试次数,防止死循环
if (delta < 1) delta = 1; // 最小调节步长
if (error > 0) { do {
// 需要增加亮度 -> 减小电位器阻值 currentLight = getCurrentLight();
lastWiperValue = max(0, lastWiperValue - delta); res = target - currentLight;
} else if (error < 0) {
// 需要减少亮度 -> 增大电位器阻值 if (abs(res) <= tolerance) {
lastWiperValue = min(127, lastWiperValue + delta); Serial.println("Target reached.");
break;
}
int lastWiperValue = digitalPot.getWiper();
if (res > 0) {
// 需要增加亮度 -> 增加电位器阻值
if (lastWiperValue <= 127) {
lastWiperValue++;
}
} else if (res < 0) {
// 需要减少亮度 -> 减少电位器阻值
if (lastWiperValue > 0) {
lastWiperValue--;
}
} }
digitalPot.setWiper(lastWiperValue); digitalPot.setWiper(lastWiperValue);
Serial.printf("Current Light: %.0f lux | Wiper Value: %d\n", currentLight, lastWiperValue); Serial.printf("Current light: %.0f, Target: %.0f, Res: %.0f, Wiper: %d\n", currentLight, target, res, lastWiperValue);
delay(200); // 等待响应,必要!!
attempt++;
// 超出最大尝试次数退出
if (attempt >= max_retries) {
Serial.println("Max attempts reached. Exiting...");
break;
}
} while (true);
} }

View File

@ -17,15 +17,14 @@ public:
// 获取当前照度 // 获取当前照度
float getCurrentLight(); float getCurrentLight();
// 执行一次调节 void runUntilTargetReached(float target, int maxAttempts = 500);
void adjustWiper();
private: private:
BH1750& lightSensor; // 引用 BH1750 传感器对象 BH1750& lightSensor; // 引用 BH1750 传感器对象
DS3502& digitalPot; // 引用 DS3502 数字电位器对象 DS3502& digitalPot; // 引用 DS3502 数字电位器对象
float targetLightLevel; // 目标照度值 float targetLightLevel; // 目标照度值
int lastWiperValue = 0; // 上一次 Wiper 值 int lastWiperValue; // 上一次 Wiper 值
float Kp = 0.5; // 比例增益(可根据实际情况调整) //float Kp = 0.5; // 比例增益
}; };
#endif #endif

View File

@ -14,8 +14,9 @@ void RunTime::begin() {
void RunTime::checkCurrent() //时长判定 void RunTime::checkCurrent() //时长判定
{ {
float current = INA.getBusVoltage(); float current = INA.getCurrent();
if (current > 10.0) { //电流大于1.0A,开始计时
if (current > 1.0) {
if (!isActive) { if (!isActive) {
// 开始计时 // 开始计时
isActive = true; isActive = true;

380
src/SHT31.cpp Normal file
View File

@ -0,0 +1,380 @@
//
// FILE: SHT31.cpp
// AUTHOR: Rob Tillaart
// VERSION: 0.5.1
// DATE: 2019-02-08
// PURPOSE: Arduino library for the SHT31 temperature and humidity sensor
// https://www.adafruit.com/product/2857
// URL: https://github.com/RobTillaart/SHT31
#include "SHT31.h"
// SUPPORTED COMMANDS - single shot mode only
static constexpr uint16_t SHT31_READ_STATUS = 0xF32D;
static constexpr uint16_t SHT31_CLEAR_STATUS = 0x3041;
static constexpr uint16_t SHT31_SOFT_RESET = 0x30A2;
static constexpr uint16_t SHT31_HARD_RESET = 0x0006;
static constexpr uint16_t SHT31_MEASUREMENT_FAST = 0x2416; // page 10 datasheet
static constexpr uint16_t SHT31_MEASUREMENT_SLOW = 0x2400; // no clock stretching
static constexpr uint16_t SHT31_HEAT_ON = 0x306D;
static constexpr uint16_t SHT31_HEAT_OFF = 0x3066;
static constexpr uint32_t SHT31_HEATER_TIMEOUT = 180000UL; // milliseconds
static constexpr uint16_t SHT31_GET_SERIAL_NUMBER = 0x3682; // no clock stretching
SHT31::SHT31(uint8_t address, TwoWire *wire)
{
_address = address;
_wire = wire;
_lastRead = 0;
_rawTemperature = 0;
_rawHumidity = 0;
_heatTimeout = 0;
_heaterStart = 0;
_heaterStop = 0;
_heaterOn = false;
_error = SHT31_OK;
}
bool SHT31::begin()
{
if ((_address != 0x44) && (_address != 0x45))
{
return false;
}
return reset();
}
bool SHT31::isConnected()
{
_wire->beginTransmission(_address);
int rv = _wire->endTransmission();
if (rv != 0) _error = SHT31_ERR_NOT_CONNECT;
return (rv == 0);
}
uint8_t SHT31::getAddress()
{
return _address;
}
bool SHT31::read(bool fast)
{
if (writeCmd(fast ? SHT31_MEASUREMENT_FAST : SHT31_MEASUREMENT_SLOW) == false)
{
return false;
}
delay(fast ? 4 : 15); // table 4 datasheet
return readData(fast);
}
/////////////////////////////////////////////////////////////////
//
// STATUS
//
#ifdef doc
// bit - description
// ==================
// 15 Alert pending status
// '0': no pending alerts
// '1': at least one pending alert - default
// 14 Reserved 0
// 13 Heater status
// '0 : Heater OFF - default
// '1 : Heater ON
// 12 Reserved '0
// 11 Humidity tracking alert
// '0 : no alert - default
// '1 : alert
// 10 Temp tracking alert
// '0 : no alert - default
// '1 : alert
// 9:5 Reserved '00000
// 4 System reset detected
// '0': no reset since last clear status register command
// '1': reset detected (hard or soft reset command or supply fail) - default
// 3:2 Reserved 00
// 1 Command status
// '0': last command executed successfully
// '1': last command not processed. Invalid or failed checksum
// 0 Write data checksum status
// '0': checksum of last write correct
// '1': checksum of last write transfer failed
#endif
uint16_t SHT31::readStatus()
{
uint8_t status[3] = { 0, 0, 0 };
// page 13 datasheet
if (writeCmd(SHT31_READ_STATUS) == false)
{
return 0xFFFF;
}
// 16 bit status + CRC
if (readBytes(3, (uint8_t*) &status[0]) == false)
{
return 0xFFFF;
}
if (status[2] != crc8(status, 2))
{
_error = SHT31_ERR_CRC_STATUS;
return 0xFFFF;
}
return (uint16_t) (status[0] << 8) + status[1];
}
// resets the following bits
// 15 Alert pending status
// 11 Humidity tracking alert
// 10 Temp tracking alert
// 4 System reset detected
bool SHT31::clearStatus()
{
if (writeCmd(SHT31_CLEAR_STATUS) == false)
{
return false;
}
return true;
}
bool SHT31::reset(bool hard)
{
bool b = writeCmd(hard ? SHT31_HARD_RESET : SHT31_SOFT_RESET);
if (b == false)
{
return false;
}
delay(1); // table 4 datasheet
return true;
}
void SHT31::setHeatTimeout(uint8_t seconds)
{
_heatTimeout = seconds;
if (_heatTimeout > 180) _heatTimeout = 180;
}
/////////////////////////////////////////////////////////////////
//
// HEATER
//
bool SHT31::heatOn()
{
if (isHeaterOn()) return true;
if ((_heaterStop > 0) && (millis() - _heaterStop < SHT31_HEATER_TIMEOUT))
{
_error = SHT31_ERR_HEATER_COOLDOWN;
return false;
}
if (writeCmd(SHT31_HEAT_ON) == false)
{
_error = SHT31_ERR_HEATER_ON;
return false;
}
_heaterStart = millis();
_heaterOn = true;
return true;
}
bool SHT31::heatOff()
{
// always switch off the heater - ignore _heaterOn flag.
if (writeCmd(SHT31_HEAT_OFF) == false)
{
_error = SHT31_ERR_HEATER_OFF; // can be serious!
return false;
}
_heaterStop = millis();
_heaterOn = false;
return true;
}
bool SHT31::isHeaterOn()
{
if (_heaterOn == false)
{
return false;
}
// did not exceed time out
if (millis() - _heaterStart < (_heatTimeout * 1000UL))
{
return true;
}
heatOff();
return false;
}
/////////////////////////////////////////////////////////////////
//
// ASYNCHRONUOUS INTERFACE
//
bool SHT31::requestData()
{
if (writeCmd(SHT31_MEASUREMENT_SLOW) == false)
{
return false;
}
_lastRequest = millis();
return true;
}
bool SHT31::dataReady()
{
return ((millis() - _lastRequest) > 15); // TODO MAGIC NR
}
bool SHT31::readData(bool fast)
{
uint8_t buffer[6];
if (readBytes(6, (uint8_t*) &buffer[0]) == false)
{
return false;
}
if (!fast)
{
if (buffer[2] != crc8(buffer, 2))
{
_error = SHT31_ERR_CRC_TEMP;
return false;
}
if (buffer[5] != crc8(buffer + 3, 2))
{
_error = SHT31_ERR_CRC_HUM;
return false;
}
}
_rawTemperature = (buffer[0] << 8) + buffer[1];
_rawHumidity = (buffer[3] << 8) + buffer[4];
_lastRead = millis();
return true;
}
/////////////////////////////////////////////////////////////////
//
// MISC
//
int SHT31::getError()
{
int rv = _error;
_error = SHT31_OK;
return rv;
}
/**
* See https://sensirion.com/media/documents/E5762713/63D103C2/Sensirion_electronic_identification_code_SHT3x.pdf
*/
bool SHT31::getSerialNumber(uint32_t &serial, bool fast) {
if (writeCmd(SHT31_GET_SERIAL_NUMBER) == false) {
return false;
}
delay(1);
uint8_t buffer[6];
if (readBytes(6, &buffer[0]) == false) {
return false;
}
if (!fast) {
if (buffer[2] != crc8(buffer, 2)) {
_error = SHT31_ERR_SERIAL_NUMBER_CRC;
return false;
}
if (buffer[5] != crc8(buffer + 3, 2)) {
_error = SHT31_ERR_SERIAL_NUMBER_CRC;
return false;
}
}
serial = buffer[0];
serial <<= 8;
serial += buffer[1];
serial <<= 8;
serial += buffer[3];
serial <<= 8;
serial += buffer[4];
return true;
}
/////////////////////////////////////////////////////////////////
//
// PROTECTED
//
uint8_t SHT31::crc8(const uint8_t *data, uint8_t len)
{
// CRC-8 formula from page 14 of SHT spec pdf
const uint8_t POLY(0x31);
uint8_t crc(0xFF);
for (uint8_t j = len; j; --j)
{
crc ^= *data++;
for (uint8_t i = 8; i; --i)
{
crc = (crc & 0x80) ? (crc << 1) ^ POLY : (crc << 1);
}
}
return crc;
}
bool SHT31::writeCmd(uint16_t cmd)
{
_wire->beginTransmission(_address);
_wire->write(cmd >> 8 );
_wire->write(cmd & 0xFF);
if (_wire->endTransmission() != 0)
{
_error = SHT31_ERR_WRITECMD;
return false;
}
_error = SHT31_OK;
return true;
}
bool SHT31::readBytes(uint8_t n, uint8_t *val)
{
int rv = _wire->requestFrom(_address, (uint8_t) n);
if (rv == n)
{
for (uint8_t i = 0; i < n; i++)
{
val[i] = _wire->read();
}
_error = SHT31_OK;
return true;
}
_error = SHT31_ERR_READBYTES;
return false;
}
// -- END OF FILE --

122
src/SHT31.h Normal file
View File

@ -0,0 +1,122 @@
#pragma once
//
// FILE: SHT31.h
// AUTHOR: Rob Tillaart
// VERSION: 0.5.2
// DATE: 2019-02-08
// PURPOSE: Arduino library for the SHT31 temperature and humidity sensor
// https://www.adafruit.com/product/2857
// URL: https://github.com/RobTillaart/SHT31
#include "Arduino.h"
#include "Wire.h"
#define SHT31_LIB_VERSION (F("0.5.2"))
#ifndef SHT_DEFAULT_ADDRESS
#define SHT_DEFAULT_ADDRESS 0x44
#endif
// fields readStatus
#define SHT31_STATUS_ALERT_PENDING (1 << 15)
#define SHT31_STATUS_HEATER_ON (1 << 13)
#define SHT31_STATUS_HUM_TRACK_ALERT (1 << 11)
#define SHT31_STATUS_TEMP_TRACK_ALERT (1 << 10)
#define SHT31_STATUS_SYSTEM_RESET (1 << 4)
#define SHT31_STATUS_COMMAND_STATUS (1 << 1)
#define SHT31_STATUS_WRITE_CRC_STATUS (1 << 0)
// error codes
#define SHT31_OK 0x00
#define SHT31_ERR_WRITECMD 0x81
#define SHT31_ERR_READBYTES 0x82
#define SHT31_ERR_HEATER_OFF 0x83
#define SHT31_ERR_NOT_CONNECT 0x84
#define SHT31_ERR_CRC_TEMP 0x85
#define SHT31_ERR_CRC_HUM 0x86
#define SHT31_ERR_CRC_STATUS 0x87
#define SHT31_ERR_HEATER_COOLDOWN 0x88
#define SHT31_ERR_HEATER_ON 0x89
#define SHT31_ERR_SERIAL_NUMBER_CRC 0x8A
class SHT31
{
public:
SHT31(uint8_t address = SHT_DEFAULT_ADDRESS, TwoWire *wire = &Wire);
bool begin();
uint8_t getAddress();
// check sensor is reachable over I2C
virtual bool isConnected();
// blocks 15 milliseconds + actual read + math
bool read(bool fast = true);
// details see datasheet; summary in SHT31.cpp file
uint16_t readStatus();
bool clearStatus();
// lastRead is in milliSeconds since start
uint32_t lastRead() { return _lastRead; };
bool reset(bool hard = false);
// do not use heater for long periods,
// use it for max 3 minutes to heat up
// and let it cool down at least 3 minutes.
void setHeatTimeout(uint8_t seconds);
uint8_t getHeatTimeout() { return _heatTimeout; };
bool heatOn();
bool heatOff();
bool isHeaterOn(); // is the sensor still heating up?
// [[deprecated("Use isHeaterOn() instead")]]
bool heatUp() { return isHeaterOn(); }; // will be obsolete
// 0..100%
float getHumidity() { return _rawHumidity * (100.0 / 65535); };
// getTemperature returns Celsius
float getTemperature() { return _rawTemperature * (175.0 / 65535) - 45; };
float getFahrenheit() { return _rawTemperature * (63.0 /13107.0) - 49; };
// raw data e.g. debugging or efficient logging / transmit.
uint16_t getRawHumidity() { return _rawHumidity; };
uint16_t getRawTemperature() { return _rawTemperature; };
// ASYNC INTERFACE
bool requestData();
bool dataReady();
bool readData(bool fast = true);
// MISC
int getError(); // clears error flag
// fast == true, => skips CRC check
bool getSerialNumber(uint32_t &serial, bool fast = true);
protected:
uint8_t _address;
uint8_t _heatTimeout; // seconds
uint32_t _lastRead;
uint32_t _lastRequest; // for async interface
uint32_t _heaterStart;
uint32_t _heaterStop;
bool _heaterOn;
uint16_t _rawHumidity;
uint16_t _rawTemperature;
uint8_t _error;
private:
uint8_t crc8(const uint8_t *data, uint8_t len);
virtual bool writeCmd(uint16_t cmd);
virtual bool readBytes(uint8_t n, uint8_t *val);
TwoWire* _wire;
};
// -- END OF FILE --

View File

@ -1,74 +1,321 @@
#include "TJC_Show.h" #include "TJC_Show.h"
#include "WiFiControl.h" #include "WiFiControl.h"
#include "LightControl.h"
extern RunTime runtime; extern RunTime runtime;
extern DS3502 ds3502; extern DS3502 ds3502;
extern BH1750 bh1750_a, bh1750_b;
extern uint16_t wiperValue; extern uint16_t wiperValue;
TJC_Show::TJC_Show(DS18B20& ds18b20, BH1750& bh1750_a, BH1750& bh1750_b, DS3502& ds3502, INA226& INA) extern void LightOpen();
: _serial(TJC_SERIAL), _ds18b20(ds18b20), _bh1750_a(bh1750_a), _bh1750_b(bh1750_b), _ds3502(ds3502), _INA(INA) {} extern void LightClose();
TJC_Show::TJC_Show(SHT31& sht31, BH1750& bh1750_a, BH1750& bh1750_b, DS3502& ds3502, INA226& INA)
: _serial(TJC_SERIAL), _sht31(sht31), _bh1750_a(bh1750_a), _bh1750_b(bh1750_b), _ds3502(ds3502), _INA(INA) {}
void TJC_Show::clearTJCSerialBuffer() {
while (_serial.read() >= 0); // 清空串口缓冲区
}
void TJC_Show::sendCommand(const char* command) {
_serial.print(command);
}
void TJC_Show::goToPage(const char* pageName) {
char cmd[32];
sprintf(cmd, "page %s\xff\xff\xff", pageName);
sendCommand(cmd);
}
void TJC_Show::init() { void TJC_Show::init() {
_serial.begin(115200, SERIAL_8N1, TJC_RX_PIN, TJC_TX_PIN); _serial.begin(115200, SERIAL_8N1, TJC_RX_PIN, TJC_TX_PIN);
clearSerialBuffer(); clearTJCSerialBuffer();
sendCommand("page main\xff\xff\xff"); //sendCommand("page main\xff\xff\xff");
goToPage("start"); // 跳转到开机页面
} }
void TJC_Show::showQR() { void TJC_Show::showQR() {
char addr[64]; char addr[64];
IPAddress ip; IPAddress ip;
#if SELECTED_WIFI_MODE == WIFI_MODE_STA
ip = WiFi.localIP();
snprintf(addr, sizeof(addr), "wifi.t0.txt=\"STA:%d.%d.%d.%d\"\xff\xff\xff", ip[0], ip[1], ip[2], ip[3]);
sendCommand(addr);
#elif SELECTED_WIFI_MODE == WIFI_MODE_AP
ip = WiFi.softAPIP();
snprintf(addr, sizeof(addr), "wifi.t0.txt=\"AP:%d.%d.%d.%d\"\xff\xff\xff", ip[0], ip[1], ip[2], ip[3]);
sendCommand(addr);
#endif
snprintf(addr, sizeof(addr), "wifi.qr0.txt=\"http://%d.%d.%d.%d/\"\xff\xff\xff", ip[0], ip[1], ip[2], ip[3]);
sendCommand(addr);
// if (SELECTED_WIFI_MODE == WIFI_MODE_STA){
// snprintf(addr, sizeof(addr), "t15.txt=\"STA:%d.%d.%d.%d\"\xff\xff\xff", ip[0], ip[1], ip[2], ip[3]);
// sendCommand(addr);
// }
// if(SELECTED_WIFI_MODE == WIFI_MODE_AP){
// snprintf(addr, sizeof(addr), "t15.txt=\"AP:%d.%d.%d.%d\"\xff\xff\xff", ip[0], ip[1], ip[2], ip[3]);
// sendCommand(addr);
// }
}
void TJC_Show::showInfo() {
char str[256];
// === main 页面 ===
snprintf(str, sizeof(str),
"main.t0.txt=\"%d℃\"\xff\xff\xff"
"main.t16.txt=\"%dRH\"\xff\xff\xff"
"main.t1.txt=\"照度A:%d\"\xff\xff\xff"
"main.t2.txt=\"照度B:%d\"\xff\xff\xff"
"main.t14.txt=\"%s\"\xff\xff\xff"
//"main.t8.txt=\"%d\"\xff\xff\xff"
"main.t9.txt=\"%.2f\"\xff\xff\xff"
//"main.t10.txt=\"%.2f\"\xff\xff\xff"
"main.t11.txt=\"%.2f\"\xff\xff\xff"
"main.t12.txt=\"%.2f\"\xff\xff\xff",
sensorData.temperature,
sensorData.humidity,
sensorData.lightA,
sensorData.lightB,
runtime.formatDuration(runtime.getActiveDuration()),
//_ds3502.getWiper(),
sensorData.busVoltage,
//sensorData.shuntVoltage_mV,
sensorData.current,
sensorData.power
);
sendCommand(str);
// === wiper 页面 ===
snprintf(str, sizeof(str),
"wiper.t1.txt=\"照度A:%d\"\xff\xff\xff"
"wiper.t2.txt=\"照度B:%d\"\xff\xff\xff"
"wiper.t9.txt=\"%.2f\"\xff\xff\xff"
//"wiper.t10.txt=\"%.2f\"\xff\xff\xff"
"wiper.t11.txt=\"%.2f\"\xff\xff\xff"
"wiper.t12.txt=\"%.2f\"\xff\xff\xff"
"wiper.t0.txt=\"%d\"\xff\xff\xff",
sensorData.lightA,
sensorData.lightB,
sensorData.busVoltage,
//sensorData.shuntVoltage_mV,
sensorData.current,
sensorData.power,
_ds3502.getWiper()
);
sendCommand(str);
// === autolight 页面 ===
snprintf(str, sizeof(str),
"autolight.t1.txt=\"照度A:%d\"\xff\xff\xff"
"autolight.t2.txt=\"照度B:%d\"\xff\xff\xff",
sensorData.lightA,
sensorData.lightB
);
sendCommand(str);
// === wifi 页面信息 ===
IPAddress ip;
#if SELECTED_WIFI_MODE == WIFI_MODE_STA #if SELECTED_WIFI_MODE == WIFI_MODE_STA
ip = WiFi.localIP(); ip = WiFi.localIP();
#elif SELECTED_WIFI_MODE == WIFI_MODE_AP #elif SELECTED_WIFI_MODE == WIFI_MODE_AP
ip = WiFi.softAPIP(); ip = WiFi.softAPIP();
#endif #endif
snprintf(addr, sizeof(addr), "qr0.txt=\"http://%d.%d.%d.%d/\"\xff\xff\xff", ip[0], ip[1], ip[2], ip[3]); snprintf(str, sizeof(str),
sendCommand(addr); "wifi.t0.txt=\"STA:%d.%d.%d.%d\"\xff\xff\xff"
"wifi.qr0.txt=\"http://%d.%d.%d.%d/\"\xff\xff\xff",
ip[0], ip[1], ip[2], ip[3],
ip[0], ip[1], ip[2], ip[3]);
sendCommand(str);
} }
// void TJC_Show::processSerial() {
// #define FRAME_LENGTH 10
// extern bool isAutoAdjustEnabled;
// extern LightControl lightcontrol;
void TJC_Show::showInfo() { // if (_serial.available() >= FRAME_LENGTH) {
// uint8_t frame_header = _serial.peek();
// if (frame_header == 0x55) {
// uint8_t buffer[FRAME_LENGTH];
// _serial.readBytes(buffer, FRAME_LENGTH);
// char str[128];
// // 验证帧尾是否正确
// if (buffer[4] == 0xff && buffer[5] == 0xff && buffer[6] == 0xff) {
// switch (buffer[1]) {
// case 0x00:
// break;
// //自动调节
// case 0x01:
// //55 01 00 01 ff ff ff含义0号打开
// //55 01 00 00 ff ff ff含义0号关闭
// // snprintf(str, sizeof(str), "msg.txt=\"led %d is %s\"\xff\xff\xff",
// // buffer[2], buffer[3] ? "on" : "off");
// // sendCommand(str);
// // isAutoAdjustEnabled == false;
// // Serial.printf("AutoLightControl is %s\n", buffer[3] ? "on" : "off");
// // if (buffer[3] == 1)
// // {
// // //isAutoAdjustEnabled = true;
// // //lightcontrol.setTargetLight(buffer[2]);
// // Serial.printf("targetLight is %d\n", buffer[2]);
// // }
// Serial.printf("targetLight is %d\n", buffer[2]);
// if (buffer[3] == 0)
// {
// isAutoAdjustEnabled = false;
// }
// break;
// //滑块设置wiper
// case 0x02:
// ds3502.setWiper(buffer[2]);
// // snprintf(str, sizeof(str), "msg.txt=\"h0.val is %d\"\xff\xff\xff", buffer[2]);
// // sendCommand(str);
// break;
// case 0x03:
// snprintf(str, sizeof(str), "msg.txt=\"h1.val is %d\"\xff\xff\xff", buffer[2]);
// sendCommand(str);
// break;
// default:
// Serial.printf("Unknown command: 0x%02X\n", buffer[1]);
// break;
// }
// }
// } else {
// _serial.read(); // 跳过无效字节
// }
// }
// }
extern LightControl lightCtrl;
void TJC_Show::processSerial() {
extern bool isAutoAdjustEnabled;
if (_serial.available() < 3) return; // 至少要有帧头 + 指令 + 帧尾标志
uint8_t frame_header = _serial.peek();
if (frame_header != 0x55) {
_serial.read(); // 跳过无效字节
return;
}
// 读掉帧头
_serial.read();
// 尝试读取命令字节
if (_serial.available() < 1) return;
uint8_t cmdByte = _serial.read();
// 定义最大可能帧长度
const int MAX_FRAME_LENGTH = 12;
uint8_t buffer[MAX_FRAME_LENGTH];
buffer[0] = 0x55;
buffer[1] = cmdByte;
// 最大尝试读取长度
int remaining = _serial.available();
int toRead = (remaining > MAX_FRAME_LENGTH - 2) ? MAX_FRAME_LENGTH - 2 : remaining;
_serial.readBytes(buffer + 2, toRead);
// 当前已读取的总字节数
int totalRead = 2 + toRead;
// 查找帧尾位置(三个连续的 0xFF
int footerPos = -1;
for (int i = 4; i < totalRead - 2; i++) {
if (buffer[i] == 0xFF && buffer[i+1] == 0xFF && buffer[i+2] == 0xFF) {
footerPos = i;
break;
}
}
if (footerPos == -1) {
Serial.println("No valid footer found.");
return;
}
// 确定帧总长度
int totalLength = footerPos + 3;
if (totalLength > totalRead) {
Serial.println("Incomplete frame received.");
return;
}
// 截断缓冲区到实际帧长度
int dataStart = 2; // 数据从 buffer[2] 开始
int dataLength = footerPos - dataStart;
char asciiStr[16] = {0};
for (int i = 0; i < dataLength; i++) {
asciiStr[i] = static_cast<char>(buffer[dataStart + i]);
}
switch (cmdByte) {
case 0x00:
//开关灯指令
//55 00 00 00 FF FF FF
//55 00 00 01 FF FF FF
if (totalLength == 7) {
if (buffer[2] == 0x00 && buffer[3] == 0x01) {
LightOpen();
} else if (buffer[2] == 0x00 && buffer[3] == 0x00) {
LightClose();
} else {
Serial.printf("Unknown cmd: %02X %02X\n", buffer[2], buffer[3]);
}
}
break;
case 0x01:
// 自动调节指令,根据帧长度输出 ASCII 字符串
// Serial.print("Received ASCII: ");
// Serial.println(asciiStr);
if(totalLength == 7 && buffer[3] == 0)
{
isAutoAdjustEnabled = false;
Serial.printf("AutoLightControl is %s\n", isAutoAdjustEnabled ? "on" : "off");
}
else{
lightCtrl.runUntilTargetReached(atoi(asciiStr), 150);
Serial.printf("Set TargetLight %d\n", atoi(asciiStr));
}
break;
case 0x02:
// 设置 wiper 值
if (totalLength == 7) {
ds3502.setWiper(buffer[2]);
}
break;
case 0x03:
// 示例:发送反馈信息
if (totalLength == 7) {
char str[128]; char str[128];
snprintf(str, sizeof(str), "msg.txt=\"h1.val is %d\"\xff\xff\xff", buffer[2]);
if (SELECTED_WIFI_MODE == WIFI_MODE_STA){
snprintf(str, sizeof(str), "t15.txt=\"MODE_STA:%s\"\xff\xff\xff", WiFi.localIP());
sendCommand(str); sendCommand(str);
} }
else if(SELECTED_WIFI_MODE == WIFI_MODE_AP){ break;
snprintf(str, sizeof(str), "t15.txt=\"MODE_AP:%s\"\xff\xff\xff", WiFi.softAPIP());
sendCommand(str); default:
Serial.printf("Unknown command: 0x%02X\n", cmdByte);
break;
} }
snprintf(str, sizeof(str), "t0.txt=\"%.2f\"\xff\xff\xff", _ds18b20.getTempC());
sendCommand(str);
snprintf(str, sizeof(str), "t1.txt=\"%.f\"\xff\xff\xff", _bh1750_a.readLightLevel());
sendCommand(str);
snprintf(str, sizeof(str), "t2.txt=\"%.f\"\xff\xff\xff", _bh1750_b.readLightLevel());
sendCommand(str);
snprintf(str, sizeof(str), "t14.txt=\"%s\"\xff\xff\xff", runtime.formatDuration(runtime.getActiveDuration()));
sendCommand(str);
snprintf(str, sizeof(str), "t8.txt=\"%d\"\xff\xff\xff", _ds3502.getWiper());
sendCommand(str);
snprintf(str, sizeof(str), "t9.txt=\"%.2f\"\xff\xff\xff", _INA.getBusVoltage());
sendCommand(str);
snprintf(str, sizeof(str), "t10.txt=\"%.2f\"\xff\xff\xff", _INA.getShuntVoltage_mV());
sendCommand(str);
snprintf(str, sizeof(str), "t11.txt=\"%.2f\"\xff\xff\xff", _INA.getCurrent());
sendCommand(str);
snprintf(str, sizeof(str), "t12.txt=\"%.2f\"\xff\xff\xff", _INA.getPower());
sendCommand(str);
}
void TJC_Show::clearSerialBuffer() {
while (_serial.read() >= 0); // 清空串口缓冲区
}
void TJC_Show::sendCommand(const char* command) {
_serial.print(command);
} }

View File

@ -3,7 +3,7 @@
#include <Arduino.h> #include <Arduino.h>
#include "BH1750.h" #include "BH1750.h"
#include "DS18B20.h" #include "SHT31.h"
#include "DS3502.h" #include "DS3502.h"
#include "INA226.h" #include "INA226.h"
#include <WiFi.h> #include <WiFi.h>
@ -14,22 +14,40 @@
#define TJC_TX_PIN 42 #define TJC_TX_PIN 42
#define TJC_RX_PIN 41 #define TJC_RX_PIN 41
struct SensorData {
int temperature;
int humidity;
int lightA;
int lightB;
float busVoltage;
float shuntVoltage_mV;
float current;
float power;
};
// 外部声明
extern SensorData sensorData;
class TJC_Show { class TJC_Show {
public: public:
TJC_Show(DS18B20& ds18b20, BH1750& bh1750_a, BH1750& bh1750_b, DS3502& DS3502, INA226& INA); TJC_Show(SHT31& sht31, BH1750& bh1750_a, BH1750& bh1750_b, DS3502& DS3502, INA226& INA);
void init(); void init();
void showQR(); void showQR();
void showInfo(); void showInfo();
void clearTJCSerialBuffer();
void sendCommand(const char* command);
void goToPage(const char* pageName);
void processSerial();
private: private:
HardwareSerial& _serial; HardwareSerial& _serial;
DS18B20& _ds18b20; SHT31& _sht31;
BH1750& _bh1750_a; BH1750& _bh1750_a;
BH1750& _bh1750_b; BH1750& _bh1750_b;
DS3502& _ds3502; DS3502& _ds3502;
INA226& _INA; INA226& _INA;
void clearSerialBuffer();
void sendCommand(const char* command);
}; };
void parseTJCCommand(unsigned char* ubuffer);
#endif // TJC_SHOW_H #endif // TJC_SHOW_H

View File

@ -4,7 +4,6 @@
#include "webpages.h" // 包含 HTML 页面内容 #include "webpages.h" // 包含 HTML 页面内容
#include <WiFi.h> #include <WiFi.h>
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
#include "DS18B20.h"
#include "BH1750.h" #include "BH1750.h"
#include "Wire.h" #include "Wire.h"
//#include "MCP45HVX1.h" //#include "MCP45HVX1.h"

View File

@ -63,6 +63,7 @@ bool connectToKnownNetworks()
void switchToAPMode() void switchToAPMode()
{ {
SELECTED_WIFI_MODE == WIFI_MODE_AP;
Serial.println("Switching to AP mode."); Serial.println("Switching to AP mode.");
WiFi.softAP(AP_SSID, AP_PASSWORD); WiFi.softAP(AP_SSID, AP_PASSWORD);
Serial.print("AP IP address: "); Serial.print("AP IP address: ");

View File

@ -1,4 +1,4 @@
#include "DS18B20.h" #include "SHT31.h"
#include "BH1750.h" #include "BH1750.h"
#include "Wire.h" #include "Wire.h"
#include <WiFi.h> #include <WiFi.h>
@ -15,6 +15,10 @@
#include "DS3502.h" #include "DS3502.h"
#include "LightControl.h" #include "LightControl.h"
#include <ArduinoJson.h>
// #include <esp_task_wdt.h>
// Ticker 用于定时检查电流状态 // Ticker 用于定时检查电流状态
Ticker currentCheckTicker; Ticker currentCheckTicker;
@ -28,17 +32,18 @@ INA226 INA(0x40);
AsyncWebServer server(80); AsyncWebServer server(80);
// DS18B20 温度传感器 // DHT11湿度传感器
#define DS18B20_PIN 8 //DHT11 dht11(8);
OneWire oneWire(DS18B20_PIN); //SHT31 温湿度
DS18B20 ds18b20(&oneWire); #define SHT31_ADDRESS 0x44
SHT31 sht31;
BH1750 bh1750_a; BH1750 bh1750_a;
BH1750 bh1750_b; BH1750 bh1750_b;
// 按键引脚 // 按键引脚
#define INCREASE_BUTTON_PIN 3 #define INCREASE_BUTTON_PIN 9
#define DECREASE_BUTTON_PIN 46 #define DECREASE_BUTTON_PIN 10
// 风扇控制引脚 // 风扇控制引脚
#define FAN_PIN1 15 #define FAN_PIN1 15
@ -47,98 +52,56 @@ BH1750 bh1750_b;
// 蜂鸣器引脚 // 蜂鸣器引脚
#define BUZZER_PIN 17 #define BUZZER_PIN 17
//灯光控制引脚
#define LIGHT_CONTROL_PIN 1
extern RunTime runtime; extern RunTime runtime;
Preferences prefs; Preferences prefs;
TJC_Show tjcShow(ds18b20, bh1750_a, bh1750_b, ds3502, INA); TJC_Show tjcShow(sht31, bh1750_a, bh1750_b, ds3502, INA);
// 比例调节bh1750_a // 比例调节bh1750_a
LightControl lightCtrl(bh1750_a, ds3502); LightControl lightCtrl(bh1750_a, ds3502);
bool autoAdjustEnabled = false; // 是否启用自动调节,默认关闭
float targetLightValue = 2000; // 默认目标照度
void WebServer_Init(void) { bool isAutoAdjustEnabled = false; // 是否启用自动调节,默认关闭
// 根路径的页面
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send_P(200, "text/html", index_html);
});
// 温度接口 SensorData sensorData;
server.on("/temperature", HTTP_GET, [](AsyncWebServerRequest *request) {
String temp = String(ds18b20.getTempC());
request->send(200, "text/plain", temp);
});
// 光照强度传感器A
server.on("/lightA", HTTP_GET, [](AsyncWebServerRequest *request) {
String lightA = String(bh1750_a.readLightLevel());
request->send(200, "text/plain", lightA);
});
// 光照传感器B unsigned long lastReadTime = 0;
server.on("/lightB", HTTP_GET, [](AsyncWebServerRequest *request) { const long readInterval = 2500; // *秒间隔
String lightB = String(bh1750_b.readLightLevel()); void UpdateSensorData(SensorData& data) {
request->send(200, "text/plain", lightB);
});
// 配置Wiper接口 sht31.read(); // default = true/fast slow = false
server.on("/wiper", HTTP_GET, [](AsyncWebServerRequest *request) { data.temperature = sht31.getTemperature(), 1;
String wiper = String(ds3502.getWiper()); data.humidity = sht31.getHumidity(), 1;
request->send(200, "text/plain", wiper); delay(100);
});
server.on("/busVoltage", HTTP_GET, [](AsyncWebServerRequest *request) { data.lightA = bh1750_a.readLightLevel();
String busVoltage = String(INA.getBusVoltage(), 3); data.lightB = bh1750_b.readLightLevel();
request->send(200, "text/plain", busVoltage); data.busVoltage = INA.getBusVoltage();
}); data.shuntVoltage_mV = INA.getShuntVoltage_mV();
data.current = INA.getCurrent();
data.power = INA.getPower();
server.on("/shuntVoltage", HTTP_GET, [](AsyncWebServerRequest *request) { // if (millis() - lastReadTime >= readInterval) {
String shuntVoltage = String(INA.getShuntVoltage_mV(), 3); // int res = dht11.readTemperatureHumidity(temp, humi);
request->send(200, "text/plain", shuntVoltage); // if(res == 0){
}); // data.temperature = temp;
// data.humidity = humi;
server.on("/current", HTTP_GET, [](AsyncWebServerRequest *request) { // }else{
String current = String(INA.getCurrent_mA(), 3); // data.temperature=0;
request->send(200, "text/plain", current); // data.humidity=0;
}); // }
// lastReadTime = millis();
server.on("/power", HTTP_GET, [](AsyncWebServerRequest *request) { // }
String power = String(INA.getPower_mW(), 3);
request->send(200, "text/plain", power);
});
// 获取累计时长接口
server.on("/duration", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/plain", runtime.formatDuration(runtime.getActiveDuration()));
});
// 清空累计时长接口
server.on("/resetDuration", HTTP_POST, [](AsyncWebServerRequest *request) {
runtime.resetActiveDuration();
request->send(200, "text/plain", "Duration reset");
});
// 接收设置Wiper的请求
server.on("/setWiper", HTTP_POST, [](AsyncWebServerRequest *request) {
if (request->hasParam("value", true)) {
int wiperValue = request->getParam("value", true)->value().toInt();
ds3502.setWiper(wiperValue);
request->send(200, "text/plain", "Wiper value set");
} else {
request->send(400, "text/plain", "Missing value parameter");
}
});
server.begin();
} }
// 风扇控制 // 风扇控制
void FansControl(void) { void FansControl(void) {
if (ds18b20.getTempC() > 30) { if (sensorData.power > 10) {
digitalWrite(FAN_PIN1, HIGH); digitalWrite(FAN_PIN1, HIGH);
digitalWrite(FAN_PIN2, HIGH); digitalWrite(FAN_PIN2, HIGH);
} else { } else {
@ -149,7 +112,7 @@ void FansControl(void) {
// 蜂鸣器 // 蜂鸣器
void Buzzer(void) { void Buzzer(void) {
if (ds18b20.getTempC() > 45) { if (sensorData.temperature > 40) {
digitalWrite(BUZZER_PIN, HIGH); digitalWrite(BUZZER_PIN, HIGH);
//tone(BUZZER_PIN, 1000, 1000); //tone(BUZZER_PIN, 1000, 1000);
} else { } else {
@ -162,11 +125,11 @@ void Buzzer(void) {
void ButtonControl(void) { void ButtonControl(void) {
// 按键检测 // 按键检测
static int lastWiperValue = ds3502.getWiper(); static int lastWiperValue = ds3502.getWiper();
static const int step = 5; // 步进值 static const int step = 1; // 步进值
if (digitalRead(INCREASE_BUTTON_PIN) == LOW) { if (digitalRead(INCREASE_BUTTON_PIN) == LOW) {
int currentWiperValue = ds3502.getWiper(); int currentWiperValue = ds3502.getWiper();
if (currentWiperValue < 127) { if (currentWiperValue < 100) {
ds3502.setWiper(currentWiperValue + step); ds3502.setWiper(currentWiperValue + step);
lastWiperValue = currentWiperValue + step; lastWiperValue = currentWiperValue + step;
Serial.printf("Increased Wiper Value: %d\n", lastWiperValue); Serial.printf("Increased Wiper Value: %d\n", lastWiperValue);
@ -185,22 +148,175 @@ void ButtonControl(void) {
} }
} }
//灯光开关控制
void LightOpen() {
digitalWrite(LIGHT_CONTROL_PIN, HIGH); // 低电平开灯【继电器默认闭合】
Serial.println("Light ON");
}
void LightClose() {
digitalWrite(LIGHT_CONTROL_PIN, LOW); // 高电平关灯
Serial.println("Light OFF");
}
void WebServer_Init(void) {
// 根路径页面
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, "text/html", index_html);
});
// 合并数据接口
server.on("/data", HTTP_GET, [](AsyncWebServerRequest *request){
StaticJsonDocument<256> doc;
doc["temperature"] = sensorData.temperature;
doc["humidity"] = sensorData.humidity;
doc["lightA"] = sensorData.lightA;
doc["lightB"] = sensorData.lightB;
doc["wiper"] = ds3502.getWiper();
doc["busVoltage"] = sensorData.busVoltage;
doc["shuntVoltage"] = sensorData.shuntVoltage_mV;
doc["current"] = sensorData.current;
doc["power"] = sensorData.power;
doc["duration"] = runtime.formatDuration(runtime.getActiveDuration());
String json;
serializeJson(doc, json);
request->send(200, "application/json", json);
});
// 设置Wiper值
server.on("/setWiper", HTTP_POST, [](AsyncWebServerRequest *request){
if (request->hasParam("value", true)) {
int wiperValue = request->getParam("value", true)->value().toInt();
ds3502.setWiper(wiperValue);
request->send(200, "text/plain", "Wiper value set");
} else {
request->send(400, "text/plain", "Missing value parameter");
}
});
// 清空累计时长
server.on("/resetDuration", HTTP_POST, [](AsyncWebServerRequest *request){
runtime.resetActiveDuration();
request->send(200, "text/plain", "Duration reset");
});
// 灯光控制接口
server.on("/LightOpen", HTTP_POST, [](AsyncWebServerRequest *request){
LightOpen();
request->send(200, "text/plain", "Light ON");
});
server.on("/LightClose", HTTP_POST, [](AsyncWebServerRequest *request){
LightClose();
request->send(200, "text/plain", "Light OFF");
});
// 获取灯光状态接口
server.on("/lightStatus", HTTP_GET, [](AsyncWebServerRequest *request){
bool isOn = (digitalRead(LIGHT_CONTROL_PIN) == HIGH); // 高电平为开灯
request->send(200, "text/plain", isOn ? "on" : "off");
});
// 启动自动调节
server.on("/startAutoAdjust", HTTP_POST, [](AsyncWebServerRequest *request){
if (request->hasParam("target", true)) {
int targetLux = request->getParam("target", true)->value().toInt();
if (targetLux > 0) {
lightCtrl.runUntilTargetReached(targetLux, 150);
//lightCtrl.setTargetLight(targetLux);
isAutoAdjustEnabled = true;
request->send(200, "text/plain", "Auto adjust started");
} else {
request->send(400, "text/plain", "Invalid target lux");
}
} else {
request->send(400, "text/plain", "Missing target parameter");
}
});
// 停止自动调节
server.on("/stopAutoAdjust", HTTP_POST, [](AsyncWebServerRequest *request){
isAutoAdjustEnabled = false;
request->send(200, "text/plain", "Auto adjust stopped");
});
// 获取自动调节状态
server.on("/autoAdjustStatus", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, "text/plain", isAutoAdjustEnabled ? "enabled" : "disabled");
});
// 启动服务器
server.begin();
}
//usb串口打印信息
// void SerialPrintInfo(void) {
// // 获取温湿度数据
// dht11.readTemperatureHumidity(temperature, humidity);
// // 获取光照数据
// float lightA = bh1750_a.readLightLevel();
// float lightB = bh1750_b.readLightLevel();
// // 获取电流监控数据
// float busVoltage = INA.getBusVoltage();
// float shuntVoltage_mV = INA.getShuntVoltage_mV();
// float current = INA.getCurrent();
// float power = INA.getPower();
// // 打印信息(只使用变量)
// Serial.printf("Temperature: %dC, Humidity: %d%%\n", temperature, humidity);
// Serial.printf("A: %.0f lux :: B: %.0f lux \n", lightA, lightB);
// Serial.println("\nBUS\tSHUNT\tCURRENT\tPOWER");
// Serial.printf("%.2f\t%.2f\t%.2f\t%.2f\n", busVoltage, shuntVoltage_mV, current, power);
// }
void SerialPrintInfo(void) {
UpdateSensorData(sensorData);
// 打印日志
Serial.printf("Temperature: %dC, Humidity: %d%%\n", sensorData.temperature, sensorData.humidity);
Serial.printf("A: %d lux :: B: %d lux \n", sensorData.lightA, sensorData.lightB);
// Serial.println("\nBUS\tSHUNT\tCURRENT\tPOWER");
// Serial.printf("%.2f\t%.2f\t%.2f\t%.2f\n",
// sensorData.busVoltage,
// sensorData.shuntVoltage_mV,
// sensorData.current,
// sensorData.power);
Serial.println("\nBUS\tCURRENT\tPOWER");
Serial.printf("%.2f\t%.2f\t%.2f\n",
sensorData.busVoltage,
//sensorData.shuntVoltage_mV,
sensorData.current,
sensorData.power);
}
void setup(void) void setup(void)
{ {
// esp_task_wdt_init(5, true); // 5秒超时触发重启
// esp_task_wdt_add(NULL); // 添加当前任务到 WDT 监控
//dht11.setDelay(150);// Set this to the desired delay. Default is 500ms.
Serial.begin(115200); Serial.begin(115200);
// 初始化 I2C 总线 // 初始化 I2C 总线
Wire.begin(5, 4); // SDA SCL Wire.begin(5, 4); // SDA SCL
sht31.begin();
//Wire.setClock(100000);
ds3502.begin(5, 4); ds3502.begin(5, 4);
// INA226 初始化 // INA226 初始化
INA.setMaxCurrentShunt(10.0, 0.005); // 设置最大电流和分流电阻 INA.setMaxCurrentShunt(10.0, 0.005); // 设置最大电流和分流电阻
// MCP45HVX1 初始化
//digiPot.begin(5, 4);
// 启动定时器,每秒检查电流状态 // 启动定时器,每秒检查电流状态
currentCheckTicker.attach(1, []() { runtime.checkCurrent(); }); currentCheckTicker.attach(1, []() { runtime.checkCurrent(); });
@ -208,7 +324,6 @@ void setup(void)
//sensor.begin(); //sensor.begin();
// BH1750 初始化 // BH1750 初始化
//bh1750_init(bh1750_a, bh1750_b, 36, 35, 21, 20); //1750初始化。sda, scl //bh1750_init(bh1750_a, bh1750_b, 36, 35, 21, 20); //1750初始化。sda, scl
//Wire.begin(36,35);
Wire1.begin(36, 35); Wire1.begin(36, 35);
bh1750_a.begin(BH1750::CONTINUOUS_HIGH_RES_MODE, 0x23, &Wire); bh1750_a.begin(BH1750::CONTINUOUS_HIGH_RES_MODE, 0x23, &Wire);
bh1750_b.begin(BH1750::CONTINUOUS_HIGH_RES_MODE, 0x23, &Wire1); bh1750_b.begin(BH1750::CONTINUOUS_HIGH_RES_MODE, 0x23, &Wire1);
@ -224,10 +339,10 @@ void setup(void)
// 串口屏初始化 // 串口屏初始化
tjcShow.init(); tjcShow.init();
tjcShow.showQR();
ds3502.setWiper(2); ds3502.setWiper(2);
delay(300); delay(200);
Serial.printf("pot: %d\n", ds3502.getWiper()); Serial.printf("pot: %d\n", ds3502.getWiper());
pinMode(FAN_PIN1, OUTPUT); pinMode(FAN_PIN1, OUTPUT);
@ -238,50 +353,38 @@ void setup(void)
pinMode(INCREASE_BUTTON_PIN, INPUT_PULLUP); pinMode(INCREASE_BUTTON_PIN, INPUT_PULLUP);
pinMode(DECREASE_BUTTON_PIN, INPUT_PULLUP); pinMode(DECREASE_BUTTON_PIN, INPUT_PULLUP);
pinMode(LIGHT_CONTROL_PIN, OUTPUT);
tjcShow.goToPage("main"); // 替代 sendCommand("page main\xff\xff\xff");
} }
unsigned long lastSensorRead = 0;
const long sensorInterval = 1000; // 每 * 秒打印一次传感器
void loop(void) void loop(void)
{ {
ds18b20.printTemperature(); //ds18b20温度 //esp_task_wdt_reset(); // 喂狗
//printLightLevels(bh1750_a, bh1750_b); //bh1750照度 if (millis() - lastSensorRead >= sensorInterval) {
Serial.printf("A: %.0f lux :: B: %.0f lux \n", SerialPrintInfo(); // 更新传感器数据
bh1750_a.readLightLevel(),
bh1750_b.readLightLevel());
// // MCP45HVX1 操作 tjcShow.showInfo(); //串口屏显示
// //digiPot.writeWiper(127); // 随机设置 Wiper 值,random(0, 256)
// ds3502.setWiper(random(0, 127)); lastSensorRead = millis();
// Serial.print("Current Wiper Value: "); }
// Serial.println(ds3502.getWiper());
// INA226 数据读取 //串口解析tjc控制指令
Serial.println("\nBUS\tSHUNT\tCURRENT\tPOWER"); tjcShow.processSerial();
Serial.print(INA.getBusVoltage(), 3); //总线电压 MAX 36V
Serial.print("\t");
Serial.print(INA.getShuntVoltage_mV(), 3); //分流电压
Serial.print("\t");
Serial.print(INA.getCurrent(), 3); //通过分流器的电流
Serial.print("\t");
Serial.print(INA.getPower(), 3); //Current x BusVoltage
Serial.println();
tjcShow.showInfo(); //串口屏
FansControl(); FansControl();
Buzzer(); Buzzer();
ButtonControl(); ButtonControl();
if (autoAdjustEnabled) { // if (isAutoAdjustEnabled) {
lightCtrl.adjustWiper(); // 比例调节 // lightCtrl.runUntilTargetReached(lightCtrl.getTargetLight(), 100); // 每次最多尝试100次
} // }
delay(1000);
} }

View File

@ -111,47 +111,38 @@ const char index_html[] PROGMEM = R"rawliteral(
<title>ESP32 Data Display</title> <title>ESP32 Data Display</title>
<meta charset="UTF-8"> <meta charset="UTF-8">
<script> <script>
async function fetchData() async function fetchData() {
{ const response = await fetch('/data');
const durationResponse = await fetch('/duration'); if (!response.ok) {
const durationText = await durationResponse.text(); alert('');
document.getElementById('activeDuration').innerText = durationText; return;
const tempResponse = await fetch('/temperature');
const tempText = await tempResponse.text();
document.getElementById('temperature').innerText = tempText;
const lightAResponse = await fetch('/lightA');
const lightAText = await lightAResponse.text();
document.getElementById('lightA').innerText = lightAText;
const lightBResponse = await fetch('/lightB');
const lightBText = await lightBResponse.text();
document.getElementById('lightB').innerText = lightBText;
const wiperResponse = await fetch('/wiper');
const wiperText = await wiperResponse.text();
document.getElementById('wiper').innerText = wiperText;
const busResponse = await fetch('/busVoltage');
const busText = await busResponse.text();
document.getElementById('busVoltage').innerText = busText;
const shuntResponse = await fetch('/shuntVoltage');
const shuntText = await shuntResponse.text();
document.getElementById('shuntVoltage').innerText = shuntText;
const currentResponse = await fetch('/current');
const currentText = await currentResponse.text();
document.getElementById('current').innerText = currentText;
const powerResponse = await fetch('/power');
const powerText = await powerResponse.text();
document.getElementById('power').innerText = powerText;
} }
async function setWiper() const data = await response.json();
{
document.getElementById('activeDuration').innerText = data.duration;
document.getElementById('temperature').innerText = data.temperature;
document.getElementById('humidity').innerText = data.humidity;
document.getElementById('lightA').innerText = data.lightA;
document.getElementById('lightB').innerText = data.lightB;
document.getElementById('wiper').innerText = data.wiper;
document.getElementById('busVoltage').innerText = data.busVoltage;
document.getElementById('shuntVoltage').innerText = data.shuntVoltage;
document.getElementById('current').innerText = data.current;
document.getElementById('power').innerText = data.power;
// 获取灯光状态
const statusResponse = await fetch('/lightStatus');
const statusText = await statusResponse.text();
document.getElementById('lightStatus').innerText = statusText === 'on' ? '' : '';
// 获取自动调节状态
const autoAdjustStatusResponse = await fetch('/autoAdjustStatus');
const autoAdjustStatusText = await autoAdjustStatusResponse.text();
document.getElementById('autoAdjustStatus').innerText = autoAdjustStatusText === 'enabled' ? '' : '';
}
async function setWiper() {
const wiperValue = document.getElementById('wiperValue').value; const wiperValue = document.getElementById('wiperValue').value;
const response = await fetch('/setWiper', { const response = await fetch('/setWiper', {
method: 'POST', method: 'POST',
@ -165,8 +156,7 @@ const char index_html[] PROGMEM = R"rawliteral(
} }
} }
async function resetDuration() async function resetDuration() {
{
const response = await fetch('/resetDuration', { method: 'POST' }); const response = await fetch('/resetDuration', { method: 'POST' });
if (response.ok) { if (response.ok) {
alert('使'); alert('使');
@ -175,28 +165,93 @@ const char index_html[] PROGMEM = R"rawliteral(
} }
} }
setInterval(fetchData, 1000); async function turnOnLight() {
const response = await fetch('/LightOpen', { method: 'POST' });
if (response.ok) {
updateLightStatus("开启");
} else {
alert('');
}
}
async function turnOffLight() {
const response = await fetch('/LightClose', { method: 'POST' });
if (response.ok) {
updateLightStatus("关闭");
} else {
alert('');
}
}
function updateLightStatus(status) {
document.getElementById('lightStatus').innerText = status;
}
async function enableAutoAdjust() {
const targetLux = document.getElementById('targetLux').value;
if (!targetLux || isNaN(targetLux) || parseInt(targetLux) <= 0) {
alert('');
return;
}
// 发送请求给 ESP32 开启自动调节并设置目标照度
const response = await fetch('/startAutoAdjust', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: 'target=' + encodeURIComponent(targetLux)
});
if (response.ok) {
alert('');
} else {
alert('');
}
}
async function disableAutoAdjust() {
const response = await fetch('/stopAutoAdjust', { method: 'POST' });
if (response.ok) {
alert('');
} else {
alert('');
}
}
setInterval(fetchData, 1000); // 每秒刷新一次数据
</script> </script>
</head> </head>
<body onload="fetchData()"> <body onload="fetchData()">
<h1></h1> <h1></h1>
<p>使: <span id="activeDuration">...</span></p> <p>使: <span id="activeDuration">...</span></p>
<button onclick="resetDuration()"></button> <button onclick="resetDuration()"></button>
<p>: <span id="temperature">...</span> °C</p> <p>: <span id="temperature">...</span> °C</p>
<p>湿: <span id="humidity">...</span> %RH</p>
<p>A: <span id="lightA">...</span> lx</p> <p>A: <span id="lightA">...</span> lx</p>
<p>B: <span id="lightB">...</span> lx</p> <p>B: <span id="lightB">...</span> lx</p>
<p>Wiper值: <span id="wiper">...</span></p> <p>Wiper值: <span id="wiper">...</span></p>
<p>线: <span id="busVoltage">...</span> V</p> <p>线: <span id="busVoltage">...</span> V</p>
<p>: <span id="shuntVoltage">...</span> mV</p> <p>: <span id="shuntVoltage">...</span> mV</p>
<p>: <span id="current">...</span> mA</p> <p>: <span id="current">...</span> A</p>
<p>: <span id="power">...</span> mW</p> <p>: <span id="power">...</span> W</p>
<h2>Wiper值</h2> <h2>Wiper值</h2>
<input type="range" id="wiperValue" min="0" max="127" step="1" value="2" /> <input type="range" id="wiperValue" min="0" max="127" step="1" value="2" />
<button onclick="setWiper()"></button> <button onclick="setWiper()"></button>
<h2></h2>
<p>: <span id="lightStatus">...</span></p>
<button onclick="turnOnLight()"></button>
<button onclick="turnOffLight()"></button>
<h2></h2>
<p>: <span id="autoAdjustStatus">...</span></p>
<p>: <input type="number" id="targetLux" min="0" max="100000" step="1" value="2000" /> lux</p>
<button onclick="enableAutoAdjust()"></button>
<button onclick="disableAutoAdjust()"></button>
</body> </body>
</html> </html>
)rawliteral"; // 添加分号 )rawliteral";
#endif // WEBPAGES_H #endif // WEBPAGES_H