rain_monitor/em3395ty/em3395ty.go
2025-07-12 17:41:49 +08:00

339 lines
9.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package em3395ty
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"rain_monitor/models"
"strconv"
"strings"
"time"
"github.com/google/uuid"
)
// 涂鸦API配置
type TuyaConfig struct {
ClientID string
Secret string
BaseURL string
}
var config TuyaConfig
// InitConfig 初始化涂鸦API配置
func InitConfig(clientID, secret, baseURL string) {
config = TuyaConfig{
ClientID: clientID,
Secret: secret,
BaseURL: baseURL,
}
log.Printf("EM3395TY API配置已初始化ClientID: %s, BaseURL: %s", clientID, baseURL)
}
// 计算HMAC-SHA256签名
func calculateSignature(secret, stringToSign string) string {
h := hmac.New(sha256.New, []byte(secret))
h.Write([]byte(stringToSign))
return hex.EncodeToString(h.Sum(nil))
}
// GetAccessToken 获取访问令牌
func GetAccessToken() (string, error) {
t := strconv.FormatInt(time.Now().UnixNano()/1e6, 10) // 13位时间戳
nonce := uuid.New().String() // 生成随机UUID
url := "/v1.0/token?grant_type=1"
// 构建stringToSign
contentSHA256 := "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" // 空body的SHA256值
stringToSign := fmt.Sprintf("GET\n%s\n\n%s", contentSHA256, url)
// 构建待签名字符串
strToHash := config.ClientID + t + nonce + stringToSign
// 使用HMAC-SHA256计算签名
signature := calculateSignature(config.Secret, strToHash)
signatureUpper := strings.ToUpper(signature)
// 构建请求头
req, err := http.NewRequest("GET", config.BaseURL+url, nil)
if err != nil {
return "", err
}
req.Header.Set("client_id", config.ClientID)
req.Header.Set("sign", signatureUpper)
req.Header.Set("sign_method", "HMAC-SHA256")
req.Header.Set("t", t)
req.Header.Set("nonce", nonce)
// 发起GET请求
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
// 解析响应
var tokenResp models.EM3395TYTokenResponse
if err := json.Unmarshal(body, &tokenResp); err != nil {
return "", err
}
if !tokenResp.Success {
return "", fmt.Errorf("获取token失败: %s", string(body))
}
return tokenResp.Result.AccessToken, nil
}
// GetDeviceInfo 获取设备信息
func GetDeviceInfo(accessToken, deviceID string) (*models.EM3395TYDeviceInfo, error) {
t := strconv.FormatInt(time.Now().UnixNano()/1e6, 10) // 13位时间戳
nonce := uuid.New().String() // 生成随机UUID
url := fmt.Sprintf("/v2.0/cloud/thing/%s", deviceID)
// 构建stringToSign
contentSHA256 := "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" // 空body的SHA256值
stringToSign := fmt.Sprintf("GET\n%s\n\n%s", contentSHA256, url)
// 构建待签名字符串
strToHash := config.ClientID + accessToken + t + nonce + stringToSign
// 使用HMAC-SHA256计算签名
signature := calculateSignature(config.Secret, strToHash)
signatureUpper := strings.ToUpper(signature)
// 构建请求头
req, err := http.NewRequest("GET", config.BaseURL+url, nil)
if err != nil {
return nil, err
}
req.Header.Set("client_id", config.ClientID)
req.Header.Set("access_token", accessToken)
req.Header.Set("sign", signatureUpper)
req.Header.Set("sign_method", "HMAC-SHA256")
req.Header.Set("t", t)
req.Header.Set("nonce", nonce)
// 发起GET请求
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
// 解析响应
var deviceInfo models.EM3395TYDeviceInfo
if err := json.Unmarshal(body, &deviceInfo); err != nil {
return nil, err
}
return &deviceInfo, nil
}
// GetDeviceStatus 获取设备状态
func GetDeviceStatus(accessToken, deviceID string) (*models.EM3395TYDeviceStatus, error) {
t := strconv.FormatInt(time.Now().UnixNano()/1e6, 10) // 13位时间戳
nonce := uuid.New().String() // 生成随机UUID
url := fmt.Sprintf("/v1.0/iot-03/devices/%s/status", deviceID)
// 构建stringToSign
contentSHA256 := "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" // 空body的SHA256值
stringToSign := fmt.Sprintf("GET\n%s\n\n%s", contentSHA256, url)
// 构建待签名字符串
strToHash := config.ClientID + accessToken + t + nonce + stringToSign
// 使用HMAC-SHA256计算签名
signature := calculateSignature(config.Secret, strToHash)
signatureUpper := strings.ToUpper(signature)
// 构建请求头
req, err := http.NewRequest("GET", config.BaseURL+url, nil)
if err != nil {
return nil, err
}
req.Header.Set("client_id", config.ClientID)
req.Header.Set("access_token", accessToken)
req.Header.Set("sign", signatureUpper)
req.Header.Set("sign_method", "HMAC-SHA256")
req.Header.Set("t", t)
req.Header.Set("nonce", nonce)
// 发起GET请求
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
// 首先解析为临时结构
var tempResponse struct {
Result []models.EM3395TYStatusItem `json:"result"`
Success bool `json:"success"`
T int64 `json:"t"`
TID string `json:"tid"`
}
if err := json.Unmarshal(body, &tempResponse); err != nil {
return nil, err
}
// 创建最终响应
deviceStatus := &models.EM3395TYDeviceStatus{
Success: tempResponse.Success,
T: tempResponse.T,
TID: tempResponse.TID,
}
// 将临时结构中的数据转换为我们的结构化数据
statusData := models.EM3395TYStatusData{}
// 遍历状态项并填充结构体
for _, item := range tempResponse.Result {
switch item.Code {
case "temp_current":
if val, ok := item.Value.(float64); ok {
statusData.TempCurrent = int(val)
}
case "humidity_value":
if val, ok := item.Value.(float64); ok {
statusData.HumidityValue = int(val)
}
case "battery_percentage":
if val, ok := item.Value.(float64); ok {
statusData.BatteryPercentage = int(val)
}
case "temp_unit_convert":
if val, ok := item.Value.(string); ok {
statusData.TempUnitConvert = val
}
case "windspeed_unit_convert":
if val, ok := item.Value.(string); ok {
statusData.WindspeedUnitConvert = val
}
case "pressure_unit_convert":
if val, ok := item.Value.(string); ok {
statusData.PressureUnitConvert = val
}
case "rain_unit_convert":
if val, ok := item.Value.(string); ok {
statusData.RainUnitConvert = val
}
case "bright_unit_convert":
if val, ok := item.Value.(string); ok {
statusData.BrightUnitConvert = val
}
case "temp_current_external":
if val, ok := item.Value.(float64); ok {
statusData.TempCurrentExternal = int(val)
}
case "humidity_outdoor":
if val, ok := item.Value.(float64); ok {
statusData.HumidityOutdoor = int(val)
}
case "temp_current_external_1":
if val, ok := item.Value.(float64); ok {
statusData.TempCurrentExternal1 = int(val)
}
case "humidity_outdoor_1":
if val, ok := item.Value.(float64); ok {
statusData.HumidityOutdoor1 = int(val)
}
case "temp_current_external_2":
if val, ok := item.Value.(float64); ok {
statusData.TempCurrentExternal2 = int(val)
}
case "humidity_outdoor_2":
if val, ok := item.Value.(float64); ok {
statusData.HumidityOutdoor2 = int(val)
}
case "temp_current_external_3":
if val, ok := item.Value.(float64); ok {
statusData.TempCurrentExternal3 = int(val)
}
case "humidity_outdoor_3":
if val, ok := item.Value.(float64); ok {
statusData.HumidityOutdoor3 = int(val)
}
case "atmospheric_pressture":
if val, ok := item.Value.(float64); ok {
statusData.AtmosphericPressure = int(val)
}
case "pressure_drop":
if val, ok := item.Value.(float64); ok {
statusData.PressureDrop = int(val)
}
case "windspeed_avg":
if val, ok := item.Value.(float64); ok {
statusData.WindspeedAvg = int(val)
}
case "windspeed_gust":
if val, ok := item.Value.(float64); ok {
statusData.WindspeedGust = int(val)
}
case "rain_1h":
if val, ok := item.Value.(float64); ok {
statusData.Rain1h = int(val)
}
case "rain_24h":
if val, ok := item.Value.(float64); ok {
statusData.Rain24h = int(val)
}
case "rain_rate":
if val, ok := item.Value.(float64); ok {
statusData.RainRate = int(val)
}
case "uv_index":
if val, ok := item.Value.(float64); ok {
statusData.UVIndex = int(val)
}
case "dew_point_temp":
if val, ok := item.Value.(float64); ok {
statusData.DewPointTemp = int(val)
}
case "feellike_temp":
if val, ok := item.Value.(float64); ok {
statusData.FeellikeTemp = int(val)
}
case "heat_index":
if val, ok := item.Value.(float64); ok {
statusData.HeatIndex = int(val)
}
case "windchill_index":
if val, ok := item.Value.(float64); ok {
statusData.WindchillIndex = int(val)
}
}
}
deviceStatus.Result = statusData
return deviceStatus, nil
}