Device model (eg. T0xAC) / 设备型号(例如:T0xAC)
T0xFA
Component version / 插件版本
0.4.5
HA core version / HA版本
2026.6.4
The problem / 问题详情
First of all: Big huge thanks for this project! I was totally amazed that this fan "just works" in Home Assistant!
There is some room for improvement and I'm happy to help make this fan work even better.
The fan remote control works, however there are a few gaps between what HA shows and what the fan actually supports:
- detailed up/down angle adjustment missing in HA (possible via MSmartHome app)
- detailed left/right angle adjustment missing in HA and MSmartHome app - can this be done with the API?
- HA control fields a bit unclear:

* what is Sleep sensor?
* swing angle should be up/down swing angle (device support 30, 60, 135 degrees)
* swing direction should be 2 separate controls: 1x left/right (swing) and 2x up/down
* voice should be "sound" or "signal" and have only on/off
Many sensors report unknown:
I actually beliefe that only the "real gear" sensor is relevant, as it shows the fan level. The others are probably not implemented on device.
Fan control should be 1-9 and on/off, not 0-100%, if possible
Entity attributes / 实体属性
device_id: "152832117900314"
sn: 0000FA52P560001F4B10639005371V60
sn8: 560001F4
model: 560001F4
device_type: T0xFA
ambient_lighting: 0
area1_gear: 0
area1_time: 0
area2_gear: 0
area2_time: 0
auto_power_off: "on"
auto_power_off_flag: 0
battery_level: 0
battery_status: invalid
connected: true
current_angle: 0
display_on_off: "on"
dust_life_time: 0
dust_reset: "off"
error_code: 0
filter_life_time: 0
filter_replace_info: invalid
filter_reset: "off"
gear: 6
humidify: invalid
lr_shake_switch: "on"
mode: normal
pm25: 0
pm25_value: 0
power: "on"
protocol_version: 6
real_gear: 6
sensorBattery: 0
sensorHumidify: 0
sensorTemperature: 0
subtype: "0"
swing: "on"
swing_angle: 1270
swing_direction: lr
target_angle: 0
temp_wind_switch: "on"
timer_off_hour: 0
timer_off_minute: 0
timer_on_hour: 0
timer_on_minute: 0
ud_angle: 60
ud_current_angle: 0
ud_diy_angle_down: 0
ud_diy_angle_up: 0
ud_shake_switch: "off"
ud_target_angle: 0
version: 1
voice: open_buzzer
water_feedback: invalid
waterions: invalid
windSpeedPercent: 0
device_class: connectivity
icon: mdi:devices
friendly_name: Fan Device Status
Home Assistant Logs / 系统日志
clean
Device LUA file / 设备对应LUA文件
local bit = require "bit"
local JSON = require "cjson"
local _bit = require "bit"
--------------常量定义-----------------------------
-- 协议版本6 ,从《美的环电-风扇-协议Ver6.0》版本开始
local protocol_version = 6
local VALUE_VERSION = 1
local modeTab = { "normal", "natural", "sleep", "comfort", "mute", "baby", "feel", "storm", "strong", "soft", "customize", "warm", "smart", "ionic", "ai_smart", "double_area", "purified_wind", "sleeping_wind", "purify_only", "self_selection","ecology","auto", [0] = "invalid" }
local humidifyTab = { "off", "no_change", "1", "2", "3", [0] = "invalid" }
local swingDirectionTab = { "lr", "ud", "w", "8", "invalid", "udlr", "custom", [0] = "invalid" }
local voiceTab = { "open_gps", "close_gps", "invalid", "open_buzzer", "open_tip", [8] = "close_buzzer", [10] = "mute" }
----------------bit-------------------
local bit = { datagBitLen = {} }
local gBitLen = 32
local isBitInit = false
local function bitInit()
if isBitInit == false then
for i = 0, gBitLen - 1 do
bit.datagBitLen[i] = 2 ^ ((gBitLen - 1) - i)
end
isBitInit = true
end
end
local function d2b(arg)
local tr = {}
for i = 0, gBitLen - 1 do
if arg >= bit.datagBitLen[i] then
tr[i] = 1
arg = arg - bit.datagBitLen[i]
else
tr[i] = 0
end
end
return tr
end
local function b2d(arg)
local nr = 0
for i = 0, gBitLen - 1 do
if arg[i] == 1 then
nr = nr + 2 ^ (gBitLen - 1 - i)
end
end
return nr
end -- bit:b2d
local function xor(a, b)
local op1 = d2b(a)
local op2 = d2b(b)
local r = {}
for i = 0, gBitLen - 1 do
if op1[i] == op2[i] then
r[i] = 0
else
r[i] = 1
end
end
return b2d(r)
end -- bit:xor
local function _and(a, b)
local op1 = d2b(a)
local op2 = d2b(b)
local r = {}
for i = 0, gBitLen - 1 do
if op1[i] == 1 and op2[i] == 1 then
r[i] = 1
else
r[i] = 0
end
end
return b2d(r)
end -- bit:_and
local function _or(a, b)
local op1 = d2b(a)
local op2 = d2b(b)
local r = {}
for i = 0, gBitLen - 1 do
if op1[i] == 1 or op2[i] == 1 then
r[i] = 1
else
r[i] = 0
end
end
return b2d(r)
end -- bit:_or
local function _not(a)
local op1 = d2b(a)
local r = {}
for i = 0, gBitLen - 1 do
if op1[i] == 1 then
r[i] = 0
else
r[i] = 1
end
end
return b2d(r)
end -- bit:_not
local function _rshift(a, n)
local op1 = d2b(a)
local r = d2b(0)
if n < gBitLen and n > 0 then
for i = 0, n - 1 do
for i = 31, 1, -1 do
op1[i] = op1[i - 1]
end
op1[0] = 0
end
r = op1
else
r = op1
end
return b2d(r)
end -- bit:_rshift
local function _lshift(a, n)
local op1 = d2b(a)
local r = d2b(0)
if n < gBitLen and n > 0 then
for i = 0, n - 1 do
for i = 0, 30 do
op1[i] = op1[i + 1]
end
op1[gBitLen - 1] = 0
end
r = op1
else
r = op1
end
return b2d(r)
end -- bit:_lshift
local function setByte(pBytes, pIndex, pValue)
pBytes[pIndex] = _and(pValue, 0xFF);
return pBytes;
end
local function getByte(pBytes, pIndex)
return _and(pBytes[pIndex], 0xFF);
end
local function _setBit(pByte, pIndex, pValue)
pByte = _and(pByte, (0xFF - _lshift(0x01, pIndex)))
pByte = _or(_lshift(_and(pValue, 0x01), pIndex), pByte);
return pByte;
end
local function _getBit(pByte, pIndex)
return _and(_rshift(pByte, pIndex), 0x01);
end
-- 获取对应索引的值,如果不存在则返回nil
local function getBit(pBytes, pIndex, pBitIndex)
if pBytes[pIndex] then
return _getBit(pBytes[pIndex], pBitIndex);
end
return nil
end
local function setBit(pBytes, pIndex, pBitIndex, pValue)
-- 健全此方法,如果索引为nil,则返回本身
if pBytes[pIndex] then
pBytes[pIndex] = _setBit(pBytes[pIndex], pBitIndex, pValue);
end
return pBytes;
end
local function _getBits(pByte, pStartIndex, pEndIndex)
if pStartIndex > pEndIndex then
return _getBits(pByte, pEndIndex, pStartIndex);
end
local tempVal = 0x00;
for i = pStartIndex, pEndIndex do
tempVal = _or(tempVal, _lshift(_getBit(pByte, i), (i - pStartIndex)));
end
return tempVal;
end
local function _setBits(pByte, pStartIndex, pEndIndex, pValue)
if pStartIndex > pEndIndex then
return _setBits(pByte, pEndIndex, pStartIndex, pValue);
end
for i = pStartIndex, pEndIndex do
pByte = _setBit(pByte, i, _getBit(pValue, i - pStartIndex));
end
return pByte;
end
-- 获取对应索引的值,如果不存在则返回nil
local function getBits(pBytes, pIndex, pBitStartIndex, pBitEndIndex)
if pBytes[pIndex] then
return _getBits(pBytes[pIndex], pBitStartIndex, pBitEndIndex);
end
return nil
end
local function setBits(pBytes, pIndex, pBitStartIndex, pBitEndIndex, pValue)
-- 健全此方法,如果索引为nil,则返回本身
if pBytes[pIndex] then
pBytes[pIndex] = _setBits(pBytes[pIndex], pBitStartIndex, pBitEndIndex,
pValue);
end
return pBytes;
end
----------------bit-------------------
-----------一些公共函数---------------
-- table 转 string
local function table2string(cmd)
local ret = ""
local i
for i = 1, #cmd do
ret = ret .. string.char(cmd[i])
end
return ret
end
-- 十六进制 string 转 table
local function string2table(hexstr)
local tb = {}
local i = 1
local j = 1
for i = 1, #hexstr - 1, 2 do
local doublebytestr = string.sub(hexstr, i, i + 1)
tb[j] = tonumber(doublebytestr, 16)
j = j + 1
end
return tb
end
-- 十六进制 string 输出
local function string2hexstring(str)
local ret = ""
for i = 1, #str do
ret = ret .. string.format("%02x", str:byte(i))
end
return ret
end
-- table 转 json
local function encode(cmd)
local tb
if JSON == nil then
JSON = require "cjson"
end
tb = JSON.encode(cmd)
return tb
end
-- json 转 table
local function decode(cmd)
local tb
if JSON == nil then
JSON = require "cjson"
end
tb = JSON.decode(cmd)
return tb
end
-- sum校验
local function makeSum(tmpbuf, start_pos, end_pos)
local resVal = 0
for si = start_pos, end_pos do
resVal = resVal + tmpbuf[si]
end
resVal = _bit.bnot(resVal) + 1
resVal = _bit.band(resVal, 0x00FF)
return resVal
end
-- CRC表
local crc8_854_table = {
0, 94, 188, 226, 97, 63, 221, 131, 194, 156, 126, 32, 163, 253, 31, 65, 157,
195, 33, 127, 252, 162, 64, 30, 95, 1, 227, 189, 62, 96, 130, 220, 35, 125,
159, 193, 66, 28, 254, 160, 225, 191, 93, 3, 128, 222, 60, 98, 190, 224, 2,
92, 223, 129, 99, 61, 124, 34, 192, 158, 29, 67, 161, 255, 70, 24, 250, 164,
39, 121, 155, 197, 132, 218, 56, 102, 229, 187, 89, 7, 219, 133, 103, 57,
186, 228, 6, 88, 25, 71, 165, 251, 120, 38, 196, 154, 101, 59, 217, 135, 4,
90, 184, 230, 167, 249, 27, 69, 198, 152, 122, 36, 248, 166, 68, 26, 153,
199, 37, 123, 58, 100, 134, 216, 91, 5, 231, 185, 140, 210, 48, 110, 237,
179, 81, 15, 78, 16, 242, 172, 47, 113, 147, 205, 17, 79, 173, 243, 112, 46,
204, 146, 211, 141, 111, 49, 178, 236, 14, 80, 175, 241, 19, 77, 206, 144,
114, 44, 109, 51, 209, 143, 12, 82, 176, 238, 50, 108, 142, 208, 83, 13,
239, 177, 240, 174, 76, 18, 145, 207, 45, 115, 202, 148, 118, 40, 171, 245,
23, 73, 8, 86, 180, 234, 105, 55, 213, 139, 87, 9, 235, 181, 54, 104, 138,
212, 149, 203, 41, 119, 244, 170, 72, 22, 233, 183, 85, 11, 136, 214, 52,
106, 43, 117, 151, 201, 74, 20, 246, 168, 116, 42, 200, 150, 21, 75, 169,
247, 182, 232, 10, 84, 215, 137, 107, 53
}
-- CRC校验
local function crc8_854(dataBuf, start_pos, end_pos)
local crc = 0
for si = start_pos, end_pos do
crc = crc8_854_table[bit.band(bit.bxor(crc, dataBuf[si]), 0xFF) + 1]
end
return crc
end
-- String转int
local function string2Int(data)
if (not data) then
data = tonumber("0")
end
data = tonumber(data)
if (data == nil) then
data = 0
end
return data
end
-- int转String
local function int2String(data)
if (not data) then
data = tostring(0)
end
data = tostring(data)
if (data == nil) then
data = "0"
end
return data
end
local function quMo(data)
if (not data) then
data = tonumber("0")
end
data = tonumber(data)
if (data == nil) then
data = 0
end
local result = 0
result = data % 10
return result
end
local function quChu(data)
if (not data) then
data = tonumber("0")
end
data = tonumber(data)
if (data == nil) then
data = 0
end
local result = 0
result = math.modf(data / 10)
return result
end
-- 从值表中获取他的索引,默认返回defaultIndex
local function getIndexFromValueTab(table, value, defaultIndex)
for k, v in pairs(table) do
if value == v then
return k
end
end
if defaultIndex then
return defaultIndex
end
return nil
end
local function getValueFromValueTab(table, index, defaultValue)
local result = table[index]
if result == nil then
result = defaultValue
end
return result
end
-----------一些公共函数---------------
-- 蓝牙传感器地址设置 {"control":{"setBtMacs":true,"btCount":3,"btMacs":"ab12ab12ab12,123456781234,123456781234""}}
local function makeSetBTMacOrder(control)
local msgBytes = {}
local msgLength = 12 + control["btCount"] * 6
for i = 0, msgLength do
msgBytes[i] = 0
end
msgBytes[0] = 0xAA
msgBytes[1] = msgLength
msgBytes[2] = 0xFA
msgBytes[8] = 2;
msgBytes[9] = 2;
msgBytes[10] = 0x0c;
msgBytes[11] = control["btCount"]
local macs = control["btMacs"]
local j = 0
local k = 0
for i = 1, msgBytes[11] do
msgBytes[k + 17] = tonumber(string.sub(macs, j + 1, j + 2), 16)
msgBytes[k + 16] = tonumber(string.sub(macs, j + 3, j + 4), 16)
msgBytes[k + 15] = tonumber(string.sub(macs, j + 5, j + 6), 16)
msgBytes[k + 14] = tonumber(string.sub(macs, j + 7, j + 8), 16)
msgBytes[k + 13] = tonumber(string.sub(macs, j + 9, j + 10), 16)
msgBytes[k + 12] = tonumber(string.sub(macs, j + 11, j + 12), 16)
k = k + 6
j = j + 13
end
msgBytes[msgLength] = makeSum(msgBytes, 1, msgLength - 1)
local infoM = {}
for i = 1, msgLength + 1 do
infoM[i] = msgBytes[i - 1]
end
local ret = table2string(infoM)
ret = string2hexstring(ret)
return ret
end
local function processSwing(msgBytes, control)
-- 上下摇头
local udSwitch = control.ud_shake_switch
if udSwitch ~= nil then
local udType, udAngle, dDiyAngle, uDiyAngle
if udSwitch == "off" then
setBit(msgBytes, 18, 7, 0)
setBits(msgBytes, 18, 1, 3, 2)
udType = 2
udAngle = 0
elseif udSwitch == "default" then
udType = 2
udAngle = 0xfe
elseif udSwitch == "normal" then
udType = 2
if control.ud_angle ~= nil then
udAngle = math.floor(tonumber(control.ud_angle) / 5)
else
udAngle = 0xff
end
elseif udSwitch == "diy" then
udType = 1
dDiyAngle = control.ud_diy_angle_down ~= nil and control.ud_diy_angle_down or 0
uDiyAngle = control.ud_diy_angle_up ~= nil and control.ud_diy_angle_up or 0
end
setBits(msgBytes, 45, 2, 3, udType)
if udType == 2 then
-- 上下diy关
msgBytes[35] = udAngle
msgBytes[64] = 0xff
msgBytes[65] = 0xff
msgBytes[66] = 0xff
msgBytes[67] = 0xff
elseif udType == 1 then
--上下diy开
msgBytes[35] = 0xff
msgBytes[64] = uDiyAngle % 256
msgBytes[65] = (uDiyAngle - msgBytes[64]) / 256
msgBytes[66] = dDiyAngle % 256
msgBytes[67] = (dDiyAngle - msgBytes[66]) / 256
end
else
setBits(msgBytes, 45, 2, 3, 0)
msgBytes[35] = 0xff
msgBytes[64] = 0xff
msgBytes[65] = 0xff
msgBytes[66] = 0xff
msgBytes[67] = 0xff
end
end
-- Json转二进制,下行解析
function jsonToData(jsonCmd)
bitInit()
if (#jsonCmd == 0) then
return nil
end
local json = decode(jsonCmd)
local query = json["query"]
local control = json["control"]
-- local status = json["status"]
local bodyLength
if (query) then
if query["type"] ~= nil and query["type"] == "btMac" then
return "aa0bfa000000000000030cec"
end
bodyLength = 0
elseif (control) then
if control["setBtMacs"] ~= nil then
return makeSetBTMacOrder(control)
end
bodyLength = 63
end
-- 消息体总长度 = 消息体前缀10字节 + 校验码1字节 (一共11个字节,因为从0开始,所以加0x0a | 10)
local msgLength = bodyLength + 0x0A
local msgBytes = {}
for i = 0, msgLength do
msgBytes[i] = 0
end
-- 构造消息部分
msgBytes[0] = 0xAA
msgBytes[1] = bodyLength + 0x0A
msgBytes[2] = 0xFA
if (query) then
msgBytes[9] = 0x03 -- 设置消息标识为03查询
elseif (control) then
msgBytes[9] = 0x02 -- 设置消息标识为02控制
-- 特殊无效值设置
msgBytes[35] = 0xff
msgBytes[36] = 0xff
msgBytes[37] = 0xff
msgBytes[38] = 0xff
msgBytes[39] = 0xff
msgBytes[48] = 0xff
msgBytes[55] = 0xff
msgBytes[56] = 0xff
for k = 61, 72 do
msgBytes[k] = 0xff
end
setBit(msgBytes, 14, 7, 1) -- 设置开关机禁止位无效(如果是开关机命令,需要将此修改为有效)
setBit(msgBytes, 18, 7, 1) -- 设置摇头禁止位无效(如果是摇头命令,需要将此修改为有效)
local numTemp = 0
processSwing(msgBytes, control)
msgBytes[33] = protocol_version
for key, value in pairs(control) do
if key == "power" then
-- 开关
if value == "on" then
setBit(msgBytes, 14, 0, 1)
setBit(msgBytes, 14, 7, 0)
elseif value == "off" then
setBit(msgBytes, 14, 0, 0)
setBit(msgBytes, 14, 7, 0)
end
elseif key == "voice" then
-- 设备语音
msgBytes[12] = getIndexFromValueTab(voiceTab, value, 0)
elseif key == "lock" then
-- 童锁
if value == "on" then
setBits(msgBytes, 13, 0, 1, 1)
elseif value == "off" then
setBits(msgBytes, 13, 0, 1, 2)
end
elseif key == "silence_ctrl" then
-- 静默控制
if value == "enable" then
setBit(msgBytes, 13, 2, 1)
elseif value == "disable" then
setBit(msgBytes, 13, 2, 0)
end
elseif key == "mode" then
local modeValue = getIndexFromValueTab(modeTab, value, 0)
setBits(msgBytes, 14, 1, 5, modeValue)
elseif key == "gear" then
-- 档位
if type(value) == "number" then
numTemp = value
else
numTemp = string2Int(value)
end
if numTemp >= 1 and numTemp <= 26 then
msgBytes[15] = numTemp
end
elseif key == "temperature" then
-- 设置温度
if type(value) == "number" then
numTemp = value
else
numTemp = string2Int(value)
end
if numTemp >= -40 and numTemp <= 50 then
msgBytes[16] = numTemp + 41
elseif numTemp == 0x80 then
msgBytes[16] = 0x80
end
elseif key == "lr_shake_switch" then
if value == "on" then
setBits(msgBytes, 18, 1, 3, 1)
setBit(msgBytes, 18, 7, 0)
setBit(msgBytes, 18, 0, 1)
msgBytes[61] = 0xfe
elseif value == "off" then
setBits(msgBytes, 18, 1, 3, 1)
setBit(msgBytes, 18, 7, 0)
setBit(msgBytes, 18, 0, 0)
msgBytes[61] = 0
end
elseif key == "swing_direction" then
-- 摇头方向
if value == "lr" then
setBits(msgBytes, 18, 1, 3, 1)
setBit(msgBytes, 18, 7, 0)
elseif value == "ud" then
setBits(msgBytes, 18, 1, 3, 2)
setBit(msgBytes, 18, 7, 0)
elseif value == "w" then
setBits(msgBytes, 18, 1, 3, 3)
setBit(msgBytes, 18, 7, 0)
elseif value == "8" then
setBits(msgBytes, 18, 1, 3, 4)
setBit(msgBytes, 18, 7, 0)
elseif value == "udlr" then
setBits(msgBytes, 18, 1, 3, 6)
setBit(msgBytes, 18, 7, 0)
elseif value == "custom" then
setBits(msgBytes, 18, 1, 3, 7)
setBit(msgBytes, 18, 7, 0)
end
elseif key == "humidity" then
-- 设置湿度
if type(value) == "number" then
numTemp = value
else
numTemp = string2Int(value)
end
if numTemp >= 1 and numTemp <= 100 then
msgBytes[17] = numTemp
end
elseif key == "humidify" then
local humidifyValue = getIndexFromValueTab(humidifyTab, value, 0);
setBits(msgBytes, 19, 4, 7, humidifyValue)
elseif key == "anophelifuge" then
-- 驱蚊
if value == "on" then
setBits(msgBytes, 19, 2, 3, 1)
elseif value == "off" then
setBits(msgBytes, 19, 2, 3, 2)
end
elseif key == "anion" then
-- 负离子
if value == "on" then
setBits(msgBytes, 19, 0, 1, 1)
elseif value == "off" then
setBits(msgBytes, 19, 0, 1, 2)
end
elseif key == "timer_off_hour" then
-- 定时关(小时)
if type(value) == "number" then
numTemp = value
else
numTemp = string2Int(value)
end
if numTemp >= 1 and numTemp <= 24 then
setBits(msgBytes, 20, 0, 4, numTemp)
end
elseif key == "timer_off_minute" then
-- 定时关(分钟)
if type(value) == "number" then
if value >= 1 and value <= 59 then
setBits(msgBytes, 20, 5, 7, quChu(value)) -- 设置分钟*10
setBits(msgBytes, 24, 4, 7, quMo(value)) -- 设置分钟
elseif value == 0 then
msgBytes[24] = 0 -- 分钟为0
end
else
if value == "clean" then
setBits(msgBytes, 20, 5, 7, 6) -- 清除定时关机
else
numTemp = string2Int(value)
if numTemp >= 1 and numTemp <= 59 then
setBits(msgBytes, 20, 5, 7, quChu(numTemp)) -- 设置分钟*10
setBits(msgBytes, 24, 4, 7, quMo(numTemp)) -- 设置分钟
elseif numTemp == 0 then
msgBytes[24] = 0 -- 分钟为0
end
end
end
elseif key == "timer_on_hour" then
-- 定时开(小时)
if type(value) == "number" then
numTemp = value
else
numTemp = string2Int(value)
end
if numTemp >= 1 and numTemp <= 24 then
setBits(msgBytes, 21, 0, 4, numTemp)
end
elseif key == "timer_on_minute" then
-- 定时开(分钟)
if type(value) == "number" then
if value >= 1 and value <= 59 then
setBits(msgBytes, 21, 5, 7, quChu(value)) -- 设置分钟*10
setBits(msgBytes, 24, 0, 3, quMo(value)) -- 设置分钟
elseif value == 0 then
msgBytes[24] = 0 -- 分钟为0
end
else
if value == "clean" then
setBits(msgBytes, 21, 5, 7, 6) -- 清除定时关机
else
numTemp = string2Int(value)
if numTemp >= 1 and numTemp <= 59 then
setBits(msgBytes, 21, 5, 7, quChu(numTemp)) -- 设置分钟*10
setBits(msgBytes, 24, 0, 3, quMo(numTemp)) -- 设置分钟
elseif numTemp == 0 then
msgBytes[24] = 0 -- 分钟为0
end
end
end
elseif key == "body_feeling_scan" then
-- 人感应扫描
if value == "on" then
msgBytes[25] = 1
elseif value == "off" then
msgBytes[25] = 2
end
elseif key == "sleep_sensor" then
-- 手环睡眠
if value == "sleep" then
msgBytes[27] = 1
elseif value == "wake" then
msgBytes[27] = 2
elseif value == "leave" then
msgBytes[27] = 3
end
-- --------------------------------------------------new below
elseif key == "spin_switch" then
-- 旋风
if value == "on" then
setBits(msgBytes, 29, 0, 1, 1)
elseif value == "off" then
setBits(msgBytes, 29, 0, 1, 2)
end
elseif key == "air_dry_switch" then
-- 湿帘风干
if value == "on" then
setBits(msgBytes, 29, 2, 3, 1)
elseif value == "off" then
setBits(msgBytes, 29, 2, 3, 2)
end
elseif key == "temp_wind_switch" then
-- 风随温变
if value == "on" then
setBits(msgBytes, 29, 4, 5, 1)
elseif value == "off" then
setBits(msgBytes, 29, 4, 5, 2)
end
elseif key == "display_on_off" then
-- 熄屏
if value == "on" then
setBits(msgBytes, 29, 6, 7, 1)
elseif value == "off" then
setBits(msgBytes, 29, 6, 7, 2)
elseif value == "on1" then
setBits(msgBytes, 29, 6, 7, 3)
end
elseif key == "breath_light" then
-- 呼吸灯
if value == "on" then
setBits(msgBytes, 34, 2, 3, 1)
elseif value == "off" then
setBits(msgBytes, 34, 2, 3, 2)
end
elseif key == "electrolytic_water" then
-- 电解水杀菌开关
if value == "on" then
setBits(msgBytes, 34, 4, 5, 1)
elseif value == "off" then
setBits(msgBytes, 34, 4, 5, 2)
end
elseif key == "auto_power_off" then
-- 15小时自动关机使能
if value == "on" then
setBits(msgBytes, 34, 6, 7, 1)
elseif value == "off" then
setBits(msgBytes, 34, 6, 7, 2)
end
elseif key == "real_gear" then
-- 真实档位
if type(value) == "number" then
numTemp = value
else
numTemp = string2Int(value)
end
if numTemp >= 0 and numTemp <= 26 then
msgBytes[40] = numTemp
end
elseif key == "back_gear" then
-- 后叶档位
if type(value) == "number" then
numTemp = value
else
numTemp = string2Int(value)
end
if numTemp >= 1 and numTemp <= 26 then
msgBytes[41] = numTemp
end
elseif key == "dust_life_time" then
-- 尘满提示时间
if type(value) == "number" then
numTemp = value
else
numTemp = string2Int(value)
end
if numTemp >= 0 and numTemp <= 255 then
msgBytes[42] = numTemp
end
elseif key == "filter_life_time" then
-- 滤网提示时间
if type(value) == "number" then
numTemp = value
else
numTemp = string2Int(value)
end
if numTemp >= 0 and numTemp <= 255 then
msgBytes[43] = numTemp
end
elseif key == "waterions" then
-- 净离子
if value == "on" then
setBits(msgBytes, 44, 0, 1, 1)
elseif value == "off" then
setBits(msgBytes, 44, 0, 1, 2)
end
elseif key == "filter_reset" then
-- 复合复位
if value == "on" then
setBit(msgBytes, 44, 6, 1)
elseif value == "off" then
setBit(msgBytes, 44, 6, 0)
end
elseif key == "dust_reset" then
-- 初效复位
if value == "on" then
setBit(msgBytes, 44, 7, 1)
elseif value == "off" then
setBit(msgBytes, 44, 7, 0)
end
elseif key == "charge" then
-- 无线充电开关
if value == "on" then
setBits(msgBytes, 45, 4, 5, 1)
elseif value == "off" then
setBits(msgBytes, 45, 4, 5, 2)
end
-- --------------------------------------------------new below
elseif key == "windSpeedPercent" and value < 101 then
msgBytes[48] = value
elseif key == "target_angle" then
msgBytes[55] = tonumber(value) % 256
msgBytes[56] = (tonumber(value) - msgBytes[55]) / 256
elseif key == "area1_time" then
msgBytes[57] = value
elseif key == "area2_time" then
msgBytes[58] = value
elseif key == "area1_gear" then
msgBytes[59] = value
elseif key == "area2_gear" then
msgBytes[60] = value
elseif key == "ud_target_angle" then
-- setBits(msgBytes, 18, 1, 3, 2)
-- setBit(msgBytes, 18, 0, 1)
-- setBit(msgBytes, 18, 7, 0)
-- msgBytes[35] = 0
msgBytes[68] = tonumber(value) % 256
msgBytes[69] = (tonumber(value) - msgBytes[68]) / 256
elseif key == "ambient_lighting" then
msgBytes[72] = tonumber(value)
end
end
end
-- 校验码
msgBytes[msgLength] = makeSum(msgBytes, 1, msgLength - 1)
local infoM = {}
for i = 1, msgLength + 1 do
infoM[i] = msgBytes[i - 1]
end
local ret = table2string(infoM)
ret = string2hexstring(ret)
return ret
end
local function processSwingToJson(msgBytes, streams)
-- 左右摇头
local swing_angle = msgBytes[61]
local swing_direction = getBits(msgBytes, 18, 1, 3)
if swing_angle == 0 then
streams["lr_shake_switch"] = "off"
else
streams["lr_shake_switch"] = "on"
end
streams["swing_angle"] = swing_angle * 5
streams["swing_direction"] = swingDirectionTab[swing_direction]
local udTypeValue = getBits(msgBytes, 45, 2, 3)
local udAngleValue = msgBytes[35]
if udTypeValue ~= 1 then
--diy 关
if udAngleValue == 0 then
streams.ud_shake_switch = "off"
elseif udAngleValue == 0xfe then
streams.ud_shake_switch = "default"
elseif udAngleValue == 0xff then
streams.ud_shake_switch = "invalid"
else
streams.ud_shake_switch = "normal"
streams.ud_angle = udAngleValue * 5
end
else
streams.ud_shake_switch = "diy"
end
streams.ud_diy_angle_down = msgBytes[66] + msgBytes[67] * 256
streams.ud_diy_angle_up = msgBytes[64] + msgBytes[65] * 256
end
-- 二进制转Json,上行解析
function dataToJson(jsonCmd)
bitInit()
if (not jsonCmd) then
return encode({ ["status"] = { ["version"] = VALUE_VERSION } })
end
local json = decode(jsonCmd)
local binData = json["msg"]["data"]
local info = {}
local msgBytes = {}
-- 二进制Tabel化(索引从1开始)
info = string2table(binData)
-- 第十位是消息类型标识|非02、03、04命令类型不处理,返回版本号
local dataType = info[10]
if ((dataType ~= 0x02) and (dataType ~= 0x03) and (dataType ~= 0x04)) then
return encode({ ["status"] = { ["version"] = VALUE_VERSION } })
end
local sub_cmd = info[11]
-- msgBytes格式化(索引从0开始)
for i = 1, #info do
msgBytes[i - 1] = info[i]
end
-- msgBytes
-- 索引0:AA
-- 索引9:消息类型标识|02 or 03 or 04
-- 索引10:消息体索引第0位
-- msgBytes[#msgBytes]:校验码|二进制最后一位
-- #msgBytes + 1:消息体总长度(1开始数数)
local streams = {}
if sub_cmd == 0 then
-- 当消息体总长等于10,意味着没有状态消息体,此时返回版本
if #msgBytes == 10 then
return encode({ ["status"] = { ["version"] = VALUE_VERSION } })
end
streams["protocol_version"] = msgBytes[33]
-- 错误报警
streams["error_code"] = msgBytes[11]
-- 设备语音
streams["voice"] = getValueFromValueTab(voiceTab, msgBytes[12], "invalid")
-- 童锁
local lock = getBits(msgBytes, 13, 0, 1)
if lock == 1 then
streams["lock"] = "on"
elseif lock == 2 then
streams["lock"] = "off"
end
-- 自动关机标志
local propertyTemp = getBit(msgBytes, 13, 3)
streams["auto_power_off_flag"] = propertyTemp
-- 开关
local power = getBit(msgBytes, 14, 0)
if power == 0 then
streams["power"] = "off"
elseif power == 1 then
streams["power"] = "on"
end
-- 模式
local mode = getBits(msgBytes, 14, 1, 5)
streams["mode"] = modeTab[mode]
-- 档位
local gear = msgBytes[15]
if gear >= 1 and gear <= 26 then
streams["gear"] = gear
end
-- 设置温度
local temperature = msgBytes[16]
if temperature >= 1 and temperature <= 91 then
streams["temperature"] = temperature - 41
elseif temperature == 0x80 then
streams["temperature"] = 0x80
end
-- 设置湿度
local humidity = msgBytes[17]
if humidity >= 1 and humidity <= 100 then
streams["humidity"] = humidity
end
-- 加湿
local humidifyValue = getBits(msgBytes, 19, 4, 7)
streams["humidify"] = humidifyTab[humidifyValue]
-- 驱蚊
local anophelifuge = getBits(msgBytes, 19, 2, 3)
if anophelifuge == 1 then
streams["anophelifuge"] = "on"
elseif anophelifuge == 2 then
streams["anophelifuge"] = "off"
end
-- 负离子
local anion = getBits(msgBytes, 19, 0, 1)
if anion == 1 then
streams["anion"] = "on"
elseif anion == 2 then
streams["anion"] = "off"
end
-- 定时关
local timer_off_hour = getBits(msgBytes, 20, 0, 4)
local timer_off_minute = getBits(msgBytes, 20, 5, 7) * 10 +
getBits(msgBytes, 24, 4, 7)
if timer_off_hour >= 0 and timer_off_hour <= 24 then
streams["timer_off_hour"] = timer_off_hour
end
if timer_off_minute >= 0 and timer_off_minute <= 59 then
streams["timer_off_minute"] = timer_off_minute
end
-- 定时开
local timer_on_hour = getBits(msgBytes, 21, 0, 4)
local timer_on_minute = getBits(msgBytes, 21, 5, 7) * 10 +
getBits(msgBytes, 24, 0, 3)
if timer_on_hour >= 0 and timer_on_hour <= 24 then
streams["timer_on_hour"] = timer_on_hour
end
if timer_on_minute >= 0 and timer_on_minute <= 59 then
streams["timer_on_minute"] = timer_on_minute
end
-- 湿度反馈
local humidify_feedback = msgBytes[22]
if humidify_feedback >= 1 and humidify_feedback <= 100 then
streams["humidify_feedback"] = humidify_feedback
end
-- 温度反馈
local temperature_feedback = msgBytes[23]
if temperature_feedback >= 1 and temperature_feedback <= 91 then
streams["temperature_feedback"] = temperature_feedback - 41
end
-- ----------------------------------------------------------------------- new blow
if (#msgBytes) > 29 then
-- 旋风
propertyTemp = getBits(msgBytes, 29, 0, 1)
if propertyTemp == 1 then
streams["spin_switch"] = "on"
elseif propertyTemp == 2 then
streams["spin_switch"] = "off"
end
-- 湿帘风干
propertyTemp = getBits(msgBytes, 29, 2, 3)
if propertyTemp == 1 then
streams["air_dry_switch"] = "on"
elseif propertyTemp == 2 then
streams["air_dry_switch"] = "off"
end
-- 风随温变
propertyTemp = getBits(msgBytes, 29, 4, 5)
if propertyTemp == 1 then
streams["temp_wind_switch"] = "on"
elseif propertyTemp == 2 then
streams["temp_wind_switch"] = "off"
end
-- 熄屏
propertyTemp = getBits(msgBytes, 29, 6, 7)
if propertyTemp == 1 then
streams["display_on_off"] = "on"
elseif propertyTemp == 2 then
streams["display_on_off"] = "off"
elseif propertyTemp == 3 then
streams["display_on_off"] = "on1"
end
end
if (#msgBytes) > 34 then
-- 水箱提醒
propertyTemp = getBits(msgBytes, 34, 0, 1)
if propertyTemp == 1 then
streams["water_feedback"] = "shortage"
elseif propertyTemp == 2 then
streams["water_feedback"] = "full"
elseif propertyTemp == 0 then
streams["water_feedback"] = "invalid"
end
-- 呼吸灯
propertyTemp = getBits(msgBytes, 34, 2, 3)
if propertyTemp == 1 then
streams["breath_light"] = "on"
elseif propertyTemp == 2 then
streams["breath_light"] = "off"
end
-- 电解水杀菌开关
propertyTemp = getBits(msgBytes, 34, 4, 5)
if propertyTemp == 1 then
streams["electrolytic_water"] = "on"
elseif propertyTemp == 2 then
streams["electrolytic_water"] = "off"
end
-- 15小时自动关机选择
propertyTemp = getBits(msgBytes, 34, 6, 7)
if propertyTemp == 1 then
streams["auto_power_off"] = "on"
elseif propertyTemp == 2 then
streams["auto_power_off"] = "off"
end
end
if (#msgBytes) > 40 then
-- 真实档位
local real_gear = msgBytes[40]
if real_gear ~= nil and real_gear >= 0 and real_gear <= 26 then
streams["real_gear"] = real_gear
end
end
if (#msgBytes) > 41 then
-- 后叶档位
local back_gear = msgBytes[41]
if back_gear ~= nil and back_gear >= 1 and back_gear <= 26 then
streams["back_gear"] = back_gear
end
end
if (#msgBytes) > 42 then
-- 尘满提示时间
local dust_life_time = msgBytes[42]
if dust_life_time ~= nil and dust_life_time >= 0 and dust_life_time <=
255 then
streams["dust_life_time"] = dust_life_time
end
end
if (#msgBytes) > 43 then
-- 滤网提示时间
local filter_life_time = msgBytes[43]
if filter_life_time ~= nil and filter_life_time >= 0 and
filter_life_time <= 255 then
streams["filter_life_time"] = filter_life_time
end
end
if (#msgBytes) > 44 then
-- 净离子
local waterions = getBits(msgBytes, 44, 0, 1)
if waterions == 1 then
streams["waterions"] = "on"
elseif waterions == 2 then
streams["waterions"] = "off"
else
streams["waterions"] = "invalid"
end
-- 滤网更换提示
local filter_replace_info = getBits(msgBytes, 44, 2, 3)
if filter_replace_info == 1 then
streams["filter_replace_info"] = "on"
elseif filter_replace_info == 2 then
streams["filter_replace_info"] = "off"
else
streams["filter_replace_info"] = "invalid"
end
-- 尘满提示
local dust_info = getBits(msgBytes, 44, 4, 5)
if dust_info == 1 then
streams["dust_info"] = "on"
elseif dust_info == 2 then
streams["dust_info"] = "off"
end
-- 复合复位
local filter_reset = getBit(msgBytes, 44, 6)
if filter_reset == 1 then
streams["filter_reset"] = "on"
elseif filter_reset == 0 then
streams["filter_reset"] = "off"
end
-- 初效复位
local dust_reset = getBit(msgBytes, 44, 7)
if dust_reset == 1 then
streams["dust_reset"] = "on"
elseif dust_reset == 0 then
streams["dust_reset"] = "off"
end
end
if (#msgBytes) > 45 then
-- 无线充电开关
local charge = getBits(msgBytes, 45, 4, 5)
if charge == 1 then
streams["charge"] = "on"
elseif charge == 2 then
streams["charge"] = "off"
end
-- 无线充电状态
local charge_feedback = getBits(msgBytes, 45, 6, 7)
if charge_feedback == 1 then
streams["charge_feedback"] = "off"
elseif charge_feedback == 2 then
streams["charge_feedback"] = "standby" -- 待机中
elseif charge_feedback == 3 then
streams["charge_feedback"] = "run" -- 运行中
end
end
if #msgBytes > 46 then
-- 电池状态
local battery_status_value = getBits(msgBytes, 46, 0, 2)
if battery_status_value == 1 then
streams["battery_status"] = "charging"
elseif battery_status_value == 2 then
streams["battery_status"] = "normal"
else
streams["battery_status"] = "invalid"
end
-- 电池电量等级
local battery_level = getBits(msgBytes, 46, 3, 7)
if battery_level < 6 then
streams["battery_level"] = battery_level
end
end
if #msgBytes > 47 and msgBytes[47] < 6 then
streams["pm25"] = msgBytes[47]
end
if #msgBytes > 52 then
if msgBytes[48] > 100 then
streams["windSpeedPercent"] = 255
else
streams["windSpeedPercent"] = msgBytes[48]
end
local sensorTemperature = (getBits(msgBytes, 50, 0, 6) * 256 + msgBytes[49]) / 10
if getBit(msgBytes, 50, 7) == 1 then
sensorTemperature = sensorTemperature * -1
end
streams["sensorTemperature"] = sensorTemperature
streams["sensorHumidify"] = msgBytes[51]
streams["sensorBattery"] = msgBytes[52]
end
if #msgBytes > 54 then
if msgBytes[53] ~= 0xff and msgBytes[54] ~= 0xff then
streams["pm25_value"] = msgBytes[53] + msgBytes[54] * 256
end
end
if #msgBytes > 60 then
streams["target_angle"] = msgBytes[55] + msgBytes[56] * 256
streams["current_angle"] = msgBytes[62] + msgBytes[63] * 256
streams["area1_time"] = msgBytes[57]
streams["area2_time"] = msgBytes[58]
streams["area1_gear"] = msgBytes[59]
streams["area2_gear"] = msgBytes[60]
streams["ud_target_angle"] = msgBytes[68] + msgBytes[69] * 256
streams["ud_current_angle"] = msgBytes[70] + msgBytes[71] * 256
streams["ambient_lighting"] = msgBytes[72]
end
processSwingToJson(msgBytes, streams)
elseif sub_cmd == 12 then
-- 蓝牙传感器地址查询或上报
streams["btCount"] = msgBytes[11]
local macs = ""
local j = 25
for i = 1, msgBytes[11] do
macs = macs .. string.sub(binData, j + 10, j + 11) .. string.sub(binData, j + 8, j + 9) .. string.sub(binData, j + 6, j + 7) .. string.sub(binData, j + 4, j + 5) .. string.sub(binData, j + 2, j + 3) .. string.sub(binData, j, j + 1)
macs = macs .. ","
j = j + 12
end
streams["btMacs"] = string.sub(macs, 1, #macs - 1)
end
-- ----------------------------------------------------------------------- new upper
-- 版本号
streams["version"] = VALUE_VERSION
local retTable = {}
retTable["status"] = streams
local ret = encode(retTable)
return ret
end
local t = [[{"deviceinfo": {"deviceType": "", "deviceSubType": "", "deviceID": ""},"msg": {"data": "AA48FA00000000000204000004000301000000000000000000000000004000000506400000000000010000000000000000000000000000000000000000000000000000000000000024"}}]]
print("解析: " .. dataToJson(t))
local test1 = [[{"control":{"ud_target_angle":60},"deviceinfo":{"deviceType":"","deviceSubType":"13","deviceID":""}}]]
print("指令" .. jsonToData(test1))
-- 导出函数
local _M = {}
function _M.jsonToData(jsonStr)
return jsonToData(jsonStr)
end
function _M.dataToJson(jsonStr)
return dataToJson(jsonStr)
end
return _M
Device Plugin file / 设备对应Plugin文件
No response
Official APP Screenshot of Device / 设备在官方APP的控制截图
No response
Device model (eg. T0xAC) / 设备型号(例如:T0xAC)
T0xFA
Component version / 插件版本
0.4.5
HA core version / HA版本
2026.6.4
The problem / 问题详情
First of all: Big huge thanks for this project! I was totally amazed that this fan "just works" in Home Assistant!
There is some room for improvement and I'm happy to help make this fan work even better.
The fan remote control works, however there are a few gaps between what HA shows and what the fan actually supports:
Many sensors report unknown:
I actually beliefe that only the "real gear" sensor is relevant, as it shows the fan level. The others are probably not implemented on device.
Fan control should be 1-9 and on/off, not 0-100%, if possible
Entity attributes / 实体属性
Home Assistant Logs / 系统日志
clean
Device LUA file / 设备对应LUA文件
local bit = require "bit"
local JSON = require "cjson"
local _bit = require "bit"
--------------常量定义-----------------------------
-- 协议版本6 ,从《美的环电-风扇-协议Ver6.0》版本开始
local protocol_version = 6
local VALUE_VERSION = 1
local modeTab = { "normal", "natural", "sleep", "comfort", "mute", "baby", "feel", "storm", "strong", "soft", "customize", "warm", "smart", "ionic", "ai_smart", "double_area", "purified_wind", "sleeping_wind", "purify_only", "self_selection","ecology","auto", [0] = "invalid" }
local humidifyTab = { "off", "no_change", "1", "2", "3", [0] = "invalid" }
local swingDirectionTab = { "lr", "ud", "w", "8", "invalid", "udlr", "custom", [0] = "invalid" }
local voiceTab = { "open_gps", "close_gps", "invalid", "open_buzzer", "open_tip", [8] = "close_buzzer", [10] = "mute" }
----------------bit-------------------
local bit = { datagBitLen = {} }
local gBitLen = 32
local isBitInit = false
local function bitInit()
if isBitInit == false then
for i = 0, gBitLen - 1 do
bit.datagBitLen[i] = 2 ^ ((gBitLen - 1) - i)
end
isBitInit = true
end
end
local function d2b(arg)
local tr = {}
for i = 0, gBitLen - 1 do
if arg >= bit.datagBitLen[i] then
tr[i] = 1
arg = arg - bit.datagBitLen[i]
else
tr[i] = 0
end
end
return tr
end
local function b2d(arg)
local nr = 0
for i = 0, gBitLen - 1 do
if arg[i] == 1 then
nr = nr + 2 ^ (gBitLen - 1 - i)
end
end
return nr
end -- bit:b2d
local function xor(a, b)
local op1 = d2b(a)
local op2 = d2b(b)
local r = {}
end -- bit:xor
local function _and(a, b)
local op1 = d2b(a)
local op2 = d2b(b)
local r = {}
for i = 0, gBitLen - 1 do
if op1[i] == 1 and op2[i] == 1 then
r[i] = 1
else
r[i] = 0
end
end
return b2d(r)
end -- bit:_and
local function _or(a, b)
local op1 = d2b(a)
local op2 = d2b(b)
local r = {}
for i = 0, gBitLen - 1 do
if op1[i] == 1 or op2[i] == 1 then
r[i] = 1
else
r[i] = 0
end
end
return b2d(r)
end -- bit:_or
local function _not(a)
local op1 = d2b(a)
local r = {}
for i = 0, gBitLen - 1 do
if op1[i] == 1 then
r[i] = 0
else
r[i] = 1
end
end
return b2d(r)
end -- bit:_not
local function _rshift(a, n)
local op1 = d2b(a)
local r = d2b(0)
if n < gBitLen and n > 0 then
for i = 0, n - 1 do
for i = 31, 1, -1 do
op1[i] = op1[i - 1]
end
op1[0] = 0
end
r = op1
else
r = op1
end
return b2d(r)
end -- bit:_rshift
local function _lshift(a, n)
local op1 = d2b(a)
local r = d2b(0)
if n < gBitLen and n > 0 then
for i = 0, n - 1 do
for i = 0, 30 do
op1[i] = op1[i + 1]
end
op1[gBitLen - 1] = 0
end
r = op1
else
r = op1
end
return b2d(r)
end -- bit:_lshift
local function setByte(pBytes, pIndex, pValue)
pBytes[pIndex] = _and(pValue, 0xFF);
return pBytes;
end
local function getByte(pBytes, pIndex)
return _and(pBytes[pIndex], 0xFF);
end
local function _setBit(pByte, pIndex, pValue)
pByte = _and(pByte, (0xFF - _lshift(0x01, pIndex)))
pByte = _or(_lshift(_and(pValue, 0x01), pIndex), pByte);
return pByte;
end
local function _getBit(pByte, pIndex)
return _and(_rshift(pByte, pIndex), 0x01);
end
-- 获取对应索引的值,如果不存在则返回nil
local function getBit(pBytes, pIndex, pBitIndex)
if pBytes[pIndex] then
return _getBit(pBytes[pIndex], pBitIndex);
end
return nil
end
local function setBit(pBytes, pIndex, pBitIndex, pValue)
-- 健全此方法,如果索引为nil,则返回本身
if pBytes[pIndex] then
pBytes[pIndex] = _setBit(pBytes[pIndex], pBitIndex, pValue);
end
return pBytes;
end
local function _getBits(pByte, pStartIndex, pEndIndex)
if pStartIndex > pEndIndex then
return _getBits(pByte, pEndIndex, pStartIndex);
end
local tempVal = 0x00;
for i = pStartIndex, pEndIndex do
tempVal = _or(tempVal, _lshift(_getBit(pByte, i), (i - pStartIndex)));
end
return tempVal;
end
local function _setBits(pByte, pStartIndex, pEndIndex, pValue)
if pStartIndex > pEndIndex then
return _setBits(pByte, pEndIndex, pStartIndex, pValue);
end
for i = pStartIndex, pEndIndex do
pByte = _setBit(pByte, i, _getBit(pValue, i - pStartIndex));
end
return pByte;
end
-- 获取对应索引的值,如果不存在则返回nil
local function getBits(pBytes, pIndex, pBitStartIndex, pBitEndIndex)
if pBytes[pIndex] then
return _getBits(pBytes[pIndex], pBitStartIndex, pBitEndIndex);
end
return nil
end
local function setBits(pBytes, pIndex, pBitStartIndex, pBitEndIndex, pValue)
-- 健全此方法,如果索引为nil,则返回本身
if pBytes[pIndex] then
pBytes[pIndex] = _setBits(pBytes[pIndex], pBitStartIndex, pBitEndIndex,
pValue);
end
return pBytes;
end
----------------bit-------------------
-----------一些公共函数---------------
-- table 转 string
local function table2string(cmd)
local ret = ""
local i
for i = 1, #cmd do
ret = ret .. string.char(cmd[i])
end
return ret
end
-- 十六进制 string 转 table
local function string2table(hexstr)
local tb = {}
local i = 1
local j = 1
for i = 1, #hexstr - 1, 2 do
local doublebytestr = string.sub(hexstr, i, i + 1)
tb[j] = tonumber(doublebytestr, 16)
j = j + 1
end
return tb
end
-- 十六进制 string 输出
local function string2hexstring(str)
local ret = ""
for i = 1, #str do
ret = ret .. string.format("%02x", str:byte(i))
end
return ret
end
-- table 转 json
local function encode(cmd)
local tb
if JSON == nil then
JSON = require "cjson"
end
tb = JSON.encode(cmd)
return tb
end
-- json 转 table
local function decode(cmd)
local tb
if JSON == nil then
JSON = require "cjson"
end
tb = JSON.decode(cmd)
return tb
end
-- sum校验
local function makeSum(tmpbuf, start_pos, end_pos)
local resVal = 0
for si = start_pos, end_pos do
resVal = resVal + tmpbuf[si]
end
resVal = _bit.bnot(resVal) + 1
resVal = _bit.band(resVal, 0x00FF)
return resVal
end
-- CRC表
local crc8_854_table = {
0, 94, 188, 226, 97, 63, 221, 131, 194, 156, 126, 32, 163, 253, 31, 65, 157,
195, 33, 127, 252, 162, 64, 30, 95, 1, 227, 189, 62, 96, 130, 220, 35, 125,
159, 193, 66, 28, 254, 160, 225, 191, 93, 3, 128, 222, 60, 98, 190, 224, 2,
92, 223, 129, 99, 61, 124, 34, 192, 158, 29, 67, 161, 255, 70, 24, 250, 164,
39, 121, 155, 197, 132, 218, 56, 102, 229, 187, 89, 7, 219, 133, 103, 57,
186, 228, 6, 88, 25, 71, 165, 251, 120, 38, 196, 154, 101, 59, 217, 135, 4,
90, 184, 230, 167, 249, 27, 69, 198, 152, 122, 36, 248, 166, 68, 26, 153,
199, 37, 123, 58, 100, 134, 216, 91, 5, 231, 185, 140, 210, 48, 110, 237,
179, 81, 15, 78, 16, 242, 172, 47, 113, 147, 205, 17, 79, 173, 243, 112, 46,
204, 146, 211, 141, 111, 49, 178, 236, 14, 80, 175, 241, 19, 77, 206, 144,
114, 44, 109, 51, 209, 143, 12, 82, 176, 238, 50, 108, 142, 208, 83, 13,
239, 177, 240, 174, 76, 18, 145, 207, 45, 115, 202, 148, 118, 40, 171, 245,
23, 73, 8, 86, 180, 234, 105, 55, 213, 139, 87, 9, 235, 181, 54, 104, 138,
212, 149, 203, 41, 119, 244, 170, 72, 22, 233, 183, 85, 11, 136, 214, 52,
106, 43, 117, 151, 201, 74, 20, 246, 168, 116, 42, 200, 150, 21, 75, 169,
247, 182, 232, 10, 84, 215, 137, 107, 53
}
-- CRC校验
local function crc8_854(dataBuf, start_pos, end_pos)
local crc = 0
for si = start_pos, end_pos do
crc = crc8_854_table[bit.band(bit.bxor(crc, dataBuf[si]), 0xFF) + 1]
end
return crc
end
-- String转int
local function string2Int(data)
if (not data) then
data = tonumber("0")
end
data = tonumber(data)
if (data == nil) then
data = 0
end
return data
end
-- int转String
local function int2String(data)
if (not data) then
data = tostring(0)
end
data = tostring(data)
if (data == nil) then
data = "0"
end
return data
end
local function quMo(data)
if (not data) then
data = tonumber("0")
end
data = tonumber(data)
if (data == nil) then
data = 0
end
local result = 0
result = data % 10
return result
end
local function quChu(data)
if (not data) then
data = tonumber("0")
end
data = tonumber(data)
if (data == nil) then
data = 0
end
local result = 0
result = math.modf(data / 10)
return result
end
-- 从值表中获取他的索引,默认返回defaultIndex
local function getIndexFromValueTab(table, value, defaultIndex)
for k, v in pairs(table) do
if value == v then
return k
end
end
if defaultIndex then
return defaultIndex
end
return nil
end
local function getValueFromValueTab(table, index, defaultValue)
local result = table[index]
if result == nil then
result = defaultValue
end
return result
end
-----------一些公共函数---------------
-- 蓝牙传感器地址设置 {"control":{"setBtMacs":true,"btCount":3,"btMacs":"ab12ab12ab12,123456781234,123456781234""}}
local function makeSetBTMacOrder(control)
local msgBytes = {}
local msgLength = 12 + control["btCount"] * 6
for i = 0, msgLength do
msgBytes[i] = 0
end
msgBytes[0] = 0xAA
msgBytes[1] = msgLength
msgBytes[2] = 0xFA
msgBytes[8] = 2;
msgBytes[9] = 2;
msgBytes[10] = 0x0c;
msgBytes[11] = control["btCount"]
local macs = control["btMacs"]
local j = 0
local k = 0
for i = 1, msgBytes[11] do
msgBytes[k + 17] = tonumber(string.sub(macs, j + 1, j + 2), 16)
msgBytes[k + 16] = tonumber(string.sub(macs, j + 3, j + 4), 16)
msgBytes[k + 15] = tonumber(string.sub(macs, j + 5, j + 6), 16)
msgBytes[k + 14] = tonumber(string.sub(macs, j + 7, j + 8), 16)
msgBytes[k + 13] = tonumber(string.sub(macs, j + 9, j + 10), 16)
msgBytes[k + 12] = tonumber(string.sub(macs, j + 11, j + 12), 16)
k = k + 6
j = j + 13
end
msgBytes[msgLength] = makeSum(msgBytes, 1, msgLength - 1)
end
local function processSwing(msgBytes, control)
end
-- Json转二进制,下行解析
function jsonToData(jsonCmd)
bitInit()
if (#jsonCmd == 0) then
return nil
end
end
local function processSwingToJson(msgBytes, streams)
-- 左右摇头
local swing_angle = msgBytes[61]
local swing_direction = getBits(msgBytes, 18, 1, 3)
if swing_angle == 0 then
streams["lr_shake_switch"] = "off"
else
streams["lr_shake_switch"] = "on"
end
streams["swing_angle"] = swing_angle * 5
streams["swing_direction"] = swingDirectionTab[swing_direction]
end
-- 二进制转Json,上行解析
function dataToJson(jsonCmd)
bitInit()
if (not jsonCmd) then
return encode({ ["status"] = { ["version"] = VALUE_VERSION } })
end
end
local t = [[{"deviceinfo": {"deviceType": "", "deviceSubType": "", "deviceID": ""},"msg": {"data": "AA48FA00000000000204000004000301000000000000000000000000004000000506400000000000010000000000000000000000000000000000000000000000000000000000000024"}}]]
print("解析: " .. dataToJson(t))
local test1 = [[{"control":{"ud_target_angle":60},"deviceinfo":{"deviceType":"","deviceSubType":"13","deviceID":""}}]]
print("指令" .. jsonToData(test1))
-- 导出函数
local _M = {}
function _M.jsonToData(jsonStr)
return jsonToData(jsonStr)
end
function _M.dataToJson(jsonStr)
return dataToJson(jsonStr)
end
return _M
Device Plugin file / 设备对应Plugin文件
No response
Official APP Screenshot of Device / 设备在官方APP的控制截图
No response