Skip to content

Instantly share code, notes, and snippets.

@hhrhhr
Created June 13, 2025 18:24
Show Gist options
  • Save hhrhhr/0d5dbf32930d1ac92ac90c0c38f74f66 to your computer and use it in GitHub Desktop.
Save hhrhhr/0d5dbf32930d1ac92ac90c0c38f74f66 to your computer and use it in GitHub Desktop.
local bit = require "bit"
local function dLog(str)
end
local SUB_TYPE_VALUE = 12
local VERSION = 9
local BYTE_PROTOCOL_HEAD = 0xAA
local BYTE_PROTOCOL_LENGTH = 0x0A
local BYTE_DEVICE_TYPE = 0xB8
local MOVEMENT_CODE = {none = 0x00, forward = 0x01, back = 0x02, left = 0x03, right = 0x04}
local CLEAN_MODE_CODE = {
none = 0x00,
random = 0x01,
arc = 0x02,
edge = 0x03,
emphases = 0x04,
screw = 0x05,
bed = 0x06,
wide_screw = 0x07,
auto = 0x08,
area = 0x09,
deep = 0x0a
}
local FAN_LEVEL_CODE = {off = 0x00, low = 0x01, normal = 0x02, high = 0x03}
local WATER_LEVEL_CODE = {off = 0x00, low = 0x01, normal = 0x02, high = 0x03}
local SPEAK_LEVEL_CODE = {none = 0x00, off = 0x01, low = 0x02, normal = 0x03, high = 0x04}
local JSON = require("cjson")
local function bitAt(bin, ops)
local tgt = bit.lshift(1, ops)
if bit.band(bin, tgt) == tgt then
return true
else
return false
end
end
local function initMessageArray(len)
local arr = {}
for i = 0, len - 1 do
arr[i] = 0
end
arr[0] = BYTE_PROTOCOL_HEAD
arr[1] = len
arr[2] = BYTE_DEVICE_TYPE
return arr
end
local function checkValidJson(j)
local rst = true
local deviceInfo = j["deviceinfo"]
if not deviceInfo then
return false
end
local deviceSubType = deviceInfo["deviceSubType"] or ""
deviceSubType = tonumber(deviceSubType) or 0
if deviceSubType ~= SUB_TYPE_VALUE then
rst = false
end
return rst
end
local function makeSum(tmpBuf, endPos)
local resVal = 0
for i = 1, endPos do
resVal = resVal + tmpBuf[i]
if resVal > 0xff then
resVal = bit.band(resVal, 0xff)
end
end
if resVal == 0 then
return 0
end
resVal = 255 - resVal + 1
return resVal
end
local function addSumAndConvertMsgToHexString(msg, len)
msg[len] = makeSum(msg, len - 1)
local bin = ""
for i = 0, len do
bin = bin .. string.format("%02x", msg[i])
end
return bin
end
local function convertWeekdayToByte(weekday)
dLog("预约日期转换 input : " .. weekday)
local rst = 0x0
for i = 1, 7 do
p, _ = string.find(weekday, tostring(i))
if p ~= nil and p > 0 then
rst = bit.bxor(rst, bit.lshift(0x01, i - 1))
end
end
rst = bit.bxor(rst, bit.lshift(0x01, 7))
dLog("预约日期转换 output: " .. rst)
return rst
end
local function convertYmdToBytes(ymd)
dLog("预约年月日转换 input : " .. ymd)
if #ymd ~= 8 then
return 0, 0, 0
end
local y = string.sub(ymd, 1, 4)
local m = string.sub(ymd, 5, 6)
local d = string.sub(ymd, 7, 8)
y = (tonumber(y) or 1900) - 1900
m = tonumber(m) or 0
d = tonumber(d) or 0
if y < 0 or y > 255 then
y = 0
end
if m < 1 or m > 12 then
m = 1
end
if d < 1 or d > 31 then
d = 1
end
dLog("预约年月日转换output : " .. y .. ":" .. m .. ":" .. d)
return y, m, d
end
local function convertTimeToBytes(time)
dLog("预约时间转换 input: " .. time)
if #time ~= 6 then
return 0, 0, 0
end
local h = string.sub(time, 1, 2)
local m = string.sub(time, 3, 4)
local s = string.sub(time, 5, 6)
h = tonumber(h) or 0
m = tonumber(m) or 0
s = tonumber(s) or 0
if h < 0 or h > 23 then
h = 0
end
if m < 0 or m > 59 then
m = 0
end
if s < 0 or s > 59 then
s = 0
end
dLog("预约时间转换output: " .. h .. ":" .. m .. ":" .. s)
return h, m, s
end
local function handleMovementJson(json)
local move = json["move_direction"] or "none"
local msgLen = 20
local msg = initMessageArray(msgLen)
msg[9] = 0x02
msg[10] = 0x22
msg[11] = 0x02
msg[13] = 0x01
msg[14] = MOVEMENT_CODE[move]
return addSumAndConvertMsgToHexString(msg, msgLen)
end
local function handleWorkModeJson(json)
local cleanMode = json["work_mode"] or "arc"
local fanLevel = json["fan_level"] or "normal"
local waterLevel = json["water_level"] or "low"
local speakLevel = json["speak_level"] or "none"
local msgLen = 20
local msg = initMessageArray(msgLen)
msg[9] = 0x02
msg[10] = 0x22
msg[11] = 0x02
msg[13] = 0x02
msg[15] = CLEAN_MODE_CODE[cleanMode]
msg[16] = FAN_LEVEL_CODE[fanLevel]
msg[18] = WATER_LEVEL_CODE[waterLevel]
msg[19] = SPEAK_LEVEL_CODE[speakLevel]
return addSumAndConvertMsgToHexString(msg, msgLen)
end
local function handleChargeStopJson(json)
local workMode = json["work_status"]
local msgLen = 13
local msg = initMessageArray(msgLen)
msg[9] = 0x02
msg[10] = 0x22
if workMode == "charge" then
msg[11] = 0x01
elseif workMode == "stop" then
msg[11] = 0x03
end
return addSumAndConvertMsgToHexString(msg, msgLen)
end
local function handleDisturbJson(json)
local value = json["disturb_switch"] or "off"
local startTime = json["disturb_start_time"]
local endTime = json["disturb_end_time"]
local msgLen = 17
local msg = initMessageArray(msgLen)
msg[9] = 0x02
msg[10] = 0x22
msg[11] = 0x06
if value == "on" then
msg[12] = 0x01
if #startTime ~= 5 or #endTime ~= 5 then
return nil
end
local startHour = string.sub(startTime, 1, 2)
local startMin = string.sub(startTime, 4, 5)
local endHour = string.sub(endTime, 1, 2)
local endMin = string.sub(endTime, 4, 5)
msg[13] = tonumber(startHour) or 0
msg[14] = tonumber(startMin) or 0
msg[15] = tonumber(endHour) or 0
msg[16] = tonumber(endMin) or 0
end
return addSumAndConvertMsgToHexString(msg, msgLen)
end
local function handleVoiceVolumeJson(json)
local value = tonumber(json["voice_level"]) or 50
if value < 0 then
value = 0
elseif value > 100 then
value = 100
end
local msgLen = 13
local msg = initMessageArray(msgLen)
msg[9] = 0x02
msg[10] = 0x22
msg[11] = 0x0a
msg[12] = value
return addSumAndConvertMsgToHexString(msg, msgLen)
end
local function handleReserveCreateJson(d)
local cnt = 1
local msgLen = 13 + 12
local msg = initMessageArray(msgLen)
msg[9] = 0x02
msg[10] = 0x24
msg[11] = 0x01
msg[12] = 0x01
local pos = 13
local weekdays = d["reserve_weekdays"] or ""
local startDate = d["reserve_start_date"] or ""
local startTime = d["reserve_start_time"] or ""
local taskMinutes = d["reserve_task_minutes"] or "60"
local cleanMode = d["reserve_work_mode"] or "arc"
local fanLevel = d["reserve_fan_level"] or "normal"
local waterLevel = d["reserve_water_level"] or "low"
local taskId = d["reserve_task_id"]
local weekdayByte = convertWeekdayToByte(weekdays)
local year, month, day = convertYmdToBytes(startDate)
local hour, min, sec = convertTimeToBytes(startTime)
local taskMin = tonumber(taskMinutes) or 0
if taskMin < 0 or taskMin > 120 then
taskMin = 0
end
cleanMode = CLEAN_MODE_CODE[cleanMode]
fanLevel = FAN_LEVEL_CODE[fanLevel]
waterLevel = WATER_LEVEL_CODE[waterLevel]
taskId = tonumber(taskId) or i
msg[pos] = weekdayByte
msg[pos + 1] = year
msg[pos + 2] = month
msg[pos + 3] = day
msg[pos + 4] = hour
msg[pos + 5] = min
msg[pos + 6] = sec
msg[pos + 7] = taskMin
msg[pos + 8] = cleanMode
msg[pos + 9] = fanLevel
msg[pos + 10] = waterLevel
msg[pos + 11] = taskId
return addSumAndConvertMsgToHexString(msg, msgLen)
end
local function handleReserveUpdateJson(action, taskId)
if action == nil or taskId == nil then
return nil
end
local tid = tonumber(taskId)
if tid == nil then
return nil
end
if tid < 1 or tid > 8 then
return nil
end
local msgLen = 25
local msg = initMessageArray(msgLen)
msg[9] = 0x02
msg[10] = 0x24
msg[11] = 0x01
if action == "delete" then
msg[12] = 0x02
elseif action == "on" then
msg[12] = 0x06
elseif action == "off" then
msg[12] = 0x05
end
msg[24] = taskId
return addSumAndConvertMsgToHexString(msg, msgLen)
end
local function handleReserveJson(json)
local action = json["reserve_action"] or ""
local taskId = json["reserve_task_id"]
if action == "set" then
return handleReserveCreateJson(json)
elseif action == "delete" or action == "on" or action == "off" then
return handleReserveUpdateJson(action, taskId)
end
return nil
end
local function handleResetJson(json)
local value = json["reset_type"] or ""
local partsReset = 0x00
local factoryReset = 0x00
if value == "factory_restore" then
factoryReset = 0x01
elseif value == "side_brush" then
partsReset = bit.lshift(1, 0)
elseif value == "filter_net" then
partsReset = bit.lshift(1, 1)
elseif value == "roll_brush" then
partsReset = bit.lshift(1, 2)
end
local msgLen = 14
local msg = initMessageArray(msgLen)
msg[9] = 0x02
msg[10] = 0x22
msg[11] = 0x07
msg[12] = partsReset
msg[13] = factoryReset
return addSumAndConvertMsgToHexString(msg, msgLen)
end
local function handleOTAJson(json)
local value = json["ota_mode"] or ""
local method = 0x00
if value == "silent" then
method = 0x00
elseif value == "command" then
method = 0x01
else
return nil
end
local msgLen = 13
local msg = initMessageArray(msgLen)
msg[9] = 0x02
msg[10] = 0x22
msg[11] = 0x09
msg[12] = method
return addSumAndConvertMsgToHexString(msg, msgLen)
end
local function handleSubTypeJson(json)
local msgLen = 30
local msg = initMessageArray(msgLen)
msg[9] = 0xA0
return addSumAndConvertMsgToHexString(msg, msgLen)
end
local function inRange(v, min, max)
if v < min or v > max then
return false
else
return true
end
end
local function handleAppTimeJson(json)
local msgLen = 18
local msg = initMessageArray(msgLen)
local year = tonumber(json["year"] or "0")
local month = tonumber(json["month"] or "1")
local day = tonumber(json["day"] or "1")
local week = tonumber(json["week"] or "0")
local hour = tonumber(json["hour"] or "0")
local minute = tonumber(json["minute"] or "0")
local second = tonumber(json["second"] or "0")
if
(inRange(year, 2000, 2099) and inRange(month, 1, 12) and inRange(day, 1, 31) and inRange(week, 0, 6) and
inRange(hour, 0, 23) and
inRange(minute, 0, 59) and
inRange(second, 0, 59)) == false
then
return nil
end
msg[9] = 0x02
msg[10] = 0x23
msg[11] = year - 2000
msg[12] = month
msg[13] = day
msg[14] = week
msg[15] = hour
msg[16] = minute
msg[17] = second
return addSumAndConvertMsgToHexString(msg, msgLen)
end
local function handleControlJson(json)
local workStatus = json["work_status"] or ""
local movement = json["move_direction"] or ""
if workStatus == "work" then
if movement ~= nil and movement ~= "none" then
return handleMovementJson(json)
else
return handleWorkModeJson(json)
end
elseif workStatus == "reserve" then
return handleReserveJson(json)
elseif workStatus == "charge" or workStatus == "stop" then
return handleChargeStopJson(json)
elseif workStatus == "disturb" then
return handleDisturbJson(json)
elseif workStatus == "voice" then
return handleVoiceVolumeJson(json)
elseif workStatus == "reset" then
return handleResetJson(json)
elseif workStatus == "ota" then
return handleOTAJson(json)
elseif workStatus == "sub" then
return handleSubTypeJson(json)
elseif workStatus == "time" then
return handleAppTimeJson(json)
end
return nil
end
local function handleQueryJson(json)
local value = json["query_type"] or "work"
local msgLen = 12
local msg = initMessageArray(msgLen)
msg[9] = 0x03
if value == "work" then
msg[10] = 0x32
msg[11] = 0x01
elseif value == "disturb" then
msg[10] = 0x32
msg[11] = 0x05
elseif value == "reserve" then
msg[10] = 0x34
msg[11] = 0x01
elseif value == "parts" then
msg[10] = 0x35
msg[11] = 0x01
end
return addSumAndConvertMsgToHexString(msg, msgLen)
end
function jsonToData(jsonStr)
if #jsonStr == 0 then
return nil
end
local j = JSON.decode(jsonStr)
if checkValidJson(j) == false then
return nil
end
if j["control"] ~= nil then
return handleControlJson(j["control"])
elseif j["query"] ~= nil then
return handleQueryJson(j["query"])
end
return nil
end
local WORK_STATUS_CODE = {
charge = 0x01,
work = 0x02,
stop = 0x03,
charging_on_dock = 0x04,
reserve_task_finished = 0x05,
charge_finish = 0x06,
charging_with_wire = 0x07
}
local CONTROL_TYPE_CODE = {none = 0x00, manual = 0x01, auto = 0x02}
local CLEAN_FUNCTION_MODE = {dust_box = 0x01, water_tanker = 0x02}
local ERR_INFRA_RED_LOW_CODE = {
none = 0x07,
none = 0x06,
infra_red_low_right_back_fall = 0x05,
infra_red_low_left_back_fall = 0x04,
infra_red_low_right_hanging = 0x03,
infra_red_low_left_hanging = 0x02,
infra_red_low_right_collision = 0x01,
infra_red_low_left_collision = 0x00,
infra_red_low_center_collision = 0x08
}
local ERR_INFRA_RED_HIGH_CODE = {
infra_red_high_left_front_obstacle = 0x07,
infra_red_high_right_front_obstacle = 0x06,
infra_red_high_front_obstacle = 0x05,
infra_red_high_left_obstacle = 0x04,
infra_red_high_right_obstacle = 0x03,
infra_red_high_right_fall = 0x02,
infra_red_high_front_fall = 0x01,
infra_red_high_left_fall = 0x00
}
local ERR_FAILURE_LOW_CODE = {
failure_low_no_dust_box = 0x07,
failure_low_dust_box_full = 0x06,
failure_low_water_tank_overload = 0x05,
failure_low_fan = 0x04,
failure_low_right_side_brush = 0x03,
failure_low_left_side_brush = 0x02,
failure_low_right_wheel_overload = 0x01,
failure_low_left_wheel_overload = 0x00
}
local ERR_FAILURE_MID_CODE = {
failure_mid_front_collision_switch = 0x07,
failure_mid_roll_brush = 0x06,
failure_mid_right_back_fall_sensor = 0x05,
failure_mid_left_back_fall_sensor = 0x04,
failure_mid_right_back_hanging_sensor = 0x03,
failure_mid_left_back_hanging_sensor = 0x02,
failure_mid_right_collision_switch = 0x01,
failure_mid_left_collision_switch = 0x00
}
local ERR_FAILURE_HIGH_CODE = {
failure_high_left_front_infra_red = 0x07,
failure_high_right_front_infra_red = 0x06,
failure_high_front_infra_red = 0x05,
failure_high_left_infra_red = 0x04,
failure_high_right_infra_red = 0x03,
failure_high_right_drop_sensor = 0x02,
failure_high_front_drop_sensor = 0x01,
failure_high_left_drop_sensor = 0x00
}
local ERR_USER_LOW_CODE = {
user_low_no_dust_box = 0x07,
user_low_dust_box_full = 0x06,
user_low_no_water_tank = 0x05,
user_low_water_pump = 0x04,
user_low_no_water = 0x03,
user_low_charging_switch_off = 0x02,
user_low_charge_error = 0x01,
user_low_network_failure = 0x00
}
local ERR_USER_MID_CODE = {
none = 0x07,
none = 0x06,
none = 0x05,
none = 0x04,
none = 0x03,
user_mid_less_battery = 0x02,
user_mid_camera = 0x01,
user_mid_engine = 0x00
}
local QUERY_STATUS_SUM_CODE = {
user_fault_sign = 0x07,
user_cmd_up_down_sign = 0x06,
none = 0x05,
none = 0x04,
none = 0x03,
user_voice_switch = 0x02,
user_wifi_light_sign = 0x01,
user_uv_light_sign = 0x00
}
local STATUS_SUM_CODE = {
user_fault_sign = 0x07,
user_cmd_up_down_sign = 0x06,
user_connect_net_sign = 0x05,
none = 0x04,
none = 0x03,
user_voice_switch = 0x02,
user_wifi_light_sign = 0x01,
user_uv_light_sign = 0x00
}
local USER_LOW_CODE = {
low_no_dust_box = 0x07,
low_dust_box_full = 0x06,
none = 0x05,
system_can_upgrade = 0x04,
low_no_water = 0x03,
low_charging_switch_off = 0x02,
low_panel_stuck = 0x01,
low_wheel_hang = 0x00
}
local USER_MID_CODE = {
none = 0x07,
roller_fault_sign = 0x06,
right_brush_fault_sign = 0x05,
left_brush_fault_sign = 0x04,
motor_fault_sign = 0x03,
right_wheel_overload = 0x02,
left_wheel_overload = 0x01,
fall_down_sign = 0x00
}
local function getCodeStr(dict, value)
for k, v in pairs(dict) do
if v == value then
return k
end
end
return "none"
end
local function convertWorkdaysFromByte(weekdayByte)
local rst = ""
for i = 1, 7 do
if bit.band(weekdayByte, bit.lshift(0x01, i - 1)) > 0 then
rst = rst .. tostring(i)
end
end
return rst
end
local function convertStringToInt(data)
local strCnt = #data
local byteCnt = strCnt / 2
local rst = {}
rst[0] = 0xaa
for i = 1, byteCnt - 1 do
local currStr = string.sub(data, i * 2 + 1, i * 2 + 2)
local value = tonumber(currStr, 16) or 0
rst[i] = value
end
return rst
end
local function wrapTableToJson(_, table)
local out = {}
table["version"] = VERSION
table["sub_type_value"] = SUB_TYPE_VALUE
out["status"] = table
return JSON.encode(out)
end
local function decodeWorkBin(bin)
local workMode = bin[11]
local controlMode = bin[13]
local moveDirection = bin[14]
local cleanMode = bin[15]
local fanLevel = bin[16]
local waterLevel = bin[18]
local speakLevel = bin[19]
local control = {}
if workMode == 0x02 then
control["work_status"] = "work"
if controlMode == 0x02 then
control["work_mode"] = getCodeStr(CLEAN_MODE_CODE, cleanMode)
elseif controlMode == 0x01 then
control["move_direction"] = getCodeStr(MOVEMENT_CODE, moveDirection)
end
control["fan_level"] = getCodeStr(FAN_LEVEL_CODE, fanLevel)
control["water_level"] = getCodeStr(WATER_LEVEL_CODE, waterLevel)
control["speak_level"] = getCodeStr(SPEAK_LEVEL_CODE, speakLevel)
end
return wrapTableToJson("status", control)
end
local function decodeChargeStopBin(bin)
local workMode = bin[11]
local control = {}
if workMode == 0x01 then
control["work_status"] = "charge"
elseif workMode == 0x03 then
control["work_status"] = "stop"
end
return wrapTableToJson("status", control)
end
local function decodeDisturbBin(bin)
local value = bin[12]
local startHour = bin[13]
local startMin = bin[14]
local endHour = bin[15]
local endMin = bin[16]
local startTime = string.format("%02d:%02d", startHour, startMin)
local endTime = string.format("%02d:%02d", endHour, endMin)
local control = {}
control["work_status"] = "disturb"
if value == 0x01 then
control["value"] = "on"
control["start_time"] = startTime
control["end_time"] = endTime
else
control["value"] = "off"
end
return wrapTableToJson("status", control)
end
local function decodeReserveBin(bin)
local action = bin[12]
local msgLen = bin[1]
local reserveCnt = (msgLen - 13) / 12
local firstTaskId = bin[24]
local control = {}
control["work_status"] = "reserve"
if action == 0x01 then
control["action"] = "set"
local data = {}
for reserveIndex = 0, reserveCnt - 1 do
local outIdx = reserveIndex + 1
data[outIdx] = {}
local pos = 13 + reserveIndex * 12
local weekdayByte = bin[pos]
local hour = bin[pos + 4]
local min = bin[pos + 5]
local sec = bin[pos + 6]
local taskMin = bin[pos + 7]
local cleanMode = bin[pos + 8]
local fanLevel = bin[pos + 9]
local waterLevel = bin[pos + 10]
local taskId = bin[pos + 11]
local switch = bin[pos + 12]
if msgLen == pos + 12 then
switch = 0
end
data[outIdx]["reserve_weekdays"] = convertWorkdaysFromByte(weekdayByte)
data[outIdx]["reserve_start_time"] = string.format("%02d%02d%02d", hour, min, sec)
data[outIdx]["reserve_task_minutes"] = tostring(taskMin)
data[outIdx]["reserve_work_mode"] = getCodeStr(CLEAN_MODE_CODE, cleanMode)
data[outIdx]["reserve_fan_level"] = getCodeStr(FAN_LEVEL_CODE, fanLevel)
data[outIdx]["reserve_water_level"] = getCodeStr(WATER_LEVEL_CODE, waterLevel)
data[outIdx]["reserve_task_id"] = tostring(taskId)
data[outIdx]["reserve_switch"] = switch == 0 and "on" or "off"
end
control["data"] = data
elseif action == 0x02 then
control["action"] = "delete"
control["task_id"] = firstTaskId
elseif action == 0x05 then
control["action"] = "off"
control["task_id"] = firstTaskId
elseif action == 0x06 then
control["action"] = "on"
control["task_id"] = firstTaskId
end
return wrapTableToJson("status", control)
end
local function decodeTimeBin(bin)
local year = bin[11]
local month = bin[12]
local day = bin[13]
local week = bin[14]
local hour = bin[15]
local minute = bin[16]
local second = bin[17]
local control = {}
control["work_status"] = "time"
control["year"] = tostring(year + 2000)
control["month"] = tostring(month)
control["day"] = tostring(day)
control["week"] = tostring(week)
control["hour"] = tostring(hour)
control["minute"] = tostring(minute)
control["second"] = tostring(second)
return wrapTableToJson("status", control)
end
local function decodeVoiceVolumeBin(bin)
local action = bin[12]
local control = {}
control["work_status"] = "voice"
control["value_level"] = tostring(action)
return wrapTableToJson("status", control)
end
local function decodeResetBin(bin)
local parts = bin[12]
local factoryReset = bin[13]
local control = {}
control["work_status"] = "reset"
if factoryReset == 0x01 then
control["reset_type"] = "factory_restore"
elseif parts == bit.lshift(1, 0) then
control["reset_type"] = "side_brush"
elseif parts == bit.lshift(1, 1) then
control["reset_type"] = "filter_net"
elseif parts == bit.lshift(1, 2) then
control["reset_type"] = "roll_brush"
end
return wrapTableToJson("status", control)
end
local function decodeOTABin(bin)
local control = {}
local mod = bin[12]
if mod == 0x01 then
control["ota_mode"] = "command"
control["ota_result"] = "succeed"
elseif mod == 0x00 then
control["ota_mode"] = "silent"
control["ota_result"] = "failed"
end
return wrapTableToJson("status", control)
end
local function decodeControlData(bin)
local msgSubType = bin[10]
local workMode = bin[11]
if msgSubType == 0x22 then
if workMode == 0x02 then
return decodeWorkBin(bin)
elseif workMode == 0x01 or workMode == 0x03 then
return decodeChargeStopBin(bin)
elseif workMode == 0x06 then
return decodeDisturbBin(bin)
elseif workMode == 0x07 then
return decodeResetBin(bin)
elseif workMode == 0x09 then
return decodeOTABin(bin)
elseif workMode == 0x0a then
return decodeVoiceVolumeBin(bin)
end
elseif msgSubType == 0x23 then
return decodeTimeBin(bin)
elseif msgSubType == 0x24 then
return decodeReserveBin(bin)
end
return bin
end
local function decodeQueryWorkStatusBin(bin)
local workStatus = bin[12]
local controlType = bin[14]
local movement = bin[15]
local cleanMode = bin[16]
local fanLevel = bin[17]
local area = bin[18]
local waterLevel = bin[19]
local voiceVolume = bin[20]
local reserveWeekday = bin[21]
local batteryRatio = bin[22]
local workMin = bin[23]
local errUserSum = bin[24] or 0
local errUserLow = bin[25] or 0
local errUserMid = bin[26] or 0
local mop = bin[27] or 0
local query = {}
query["query_type"] = "work"
if controlType == nil then
return wrapTableToJson("status", query)
end
for i = 0, 7 do
local keySum = getCodeStr(QUERY_STATUS_SUM_CODE, i)
local valueSum = bitAt(errUserSum, i)
if keySum ~= nil and keySum ~= "none" then
query[keySum] = valueSum == true and "yes" or "no"
end
local keyLow = getCodeStr(USER_LOW_CODE, i)
local valueLow = bitAt(errUserLow, i)
if keyLow ~= nil and keyLow ~= "none" then
query[keyLow] = valueLow == true and "yes" or "no"
end
local keyMid = getCodeStr(USER_MID_CODE, i)
local valueMid = bitAt(errUserMid, i)
if keyMid ~= nil and keyMid ~= "none" then
query[keyMid] = valueMid == true and "yes" or "no"
end
end
query["work_status"] = getCodeStr(WORK_STATUS_CODE, workStatus)
query["control_type"] = getCodeStr(CONTROL_TYPE_CODE, controlType)
query["move_direction"] = getCodeStr(MOVEMENT_CODE, movement)
query["work_mode"] = getCodeStr(CLEAN_MODE_CODE, cleanMode)
query["fan_level"] = getCodeStr(FAN_LEVEL_CODE, fanLevel)
query["area"] = tostring(area)
query["water_level"] = getCodeStr(WATER_LEVEL_CODE, waterLevel)
query["voice_level"] = tostring(voiceVolume)
if reserveWeekday == 0 then
query["have_reserve_task"] = "0"
else
query["have_reserve_task"] = "1"
end
query["battery_percent"] = tostring(batteryRatio)
query["work_time"] = tostring(workMin)
query["mop"] = mop == 0x1 and "yes" or "no"
return wrapTableToJson("status", query)
end
local function decodeQueryDisturbBin(bin)
local setStatus = bin[12] or 0
local startHour = bin[13] or 0
local startMin = bin[14] or 0
local endHour = bin[15] or 0
local endMin = bin[16] or 0
if bin[1] == 12 then
local query = {}
query["query"] = "disturb"
return wrapTableToJson("query", query)
end
local query = {}
query["query_type"] = "disturb"
if setStatus == 0x00 then
query["set_status"] = "off"
else
query["set_status"] = "on"
query["start_time"] = string.format("%02d:%02d", startHour, startMin)
query["end_time"] = string.format("%02d:%02d", endHour, endMin)
end
return wrapTableToJson("status", query)
end
local function decodeQueryObserverBin(bin)
if bin[1] == 0x0c then
local query = {}
query["query"] = "reserve"
return wrapTableToJson("query", query)
end
local reserveCnt = (bin[01] - 13) / 9
local query = {}
query["query_type"] = "reserve"
local data = {}
if reserveCnt > 0 then
for i = 0, reserveCnt - 1 do
local pos = 13 + 9 * i
local weekdayByte = bin[pos]
local hour = bin[pos + 1]
local min = bin[pos + 2]
local taskMin = bin[pos + 3]
local cleanMode = bin[pos + 4]
local fanLevel = bin[pos + 5]
local waterLevel = bin[pos + 6]
local taskId = bin[pos + 7]
local isOpen = bin[pos + 8]
data[i + 1] = {}
data[i + 1]["weekdays"] = convertWorkdaysFromByte(weekdayByte)
data[i + 1]["start_time"] = string.format("%02d%02d", hour, min)
data[i + 1]["task_minutes"] = tostring(taskMin)
data[i + 1]["work_mode"] = getCodeStr(CLEAN_MODE_CODE, cleanMode)
data[i + 1]["fan_level"] = getCodeStr(FAN_LEVEL_CODE, fanLevel)
data[i + 1]["water_level"] = getCodeStr(WATER_LEVEL_CODE, waterLevel)
data[i + 1]["task_id"] = tostring(taskId)
data[i + 1]["open_status"] = isOpen == 0x00 and "on" or "off"
end
end
query["data"] = data
return wrapTableToJson("status", query)
end
local function decodeQueryParts(bin)
if bin[1] == 0x0c then
local query = {}
query["query"] = "parts"
return wrapTableToJson("query", query)
end
local sideBrushRestTime = bit.lshift(bin[13], 8) + bin[12]
local sideBrushLifeTime = bit.lshift(bin[15], 8) + bin[14]
local filterNetRestTime = bit.lshift(bin[17], 8) + bin[16]
local filterNetLifeTime = bit.lshift(bin[19], 8) + bin[18]
local rollBrushRestTime = bit.lshift(bin[21], 8) + bin[20]
local rollBrushLifeTime = bit.lshift(bin[23], 8) + bin[22]
local query = {}
query["side_brush_rest_time"] = tostring(sideBrushRestTime)
query["side_brush_life_time"] = tostring(sideBrushLifeTime)
query["filt_brNet_rest_time"] = tostring(filterNetRestTime)
query["filt_brNet_life_time"] = tostring(filterNetLifeTime)
query["roll_brush_rest_time"] = tostring(rollBrushRestTime)
query["roll_brush_life_time"] = tostring(rollBrushLifeTime)
return wrapTableToJson("status", query)
end
local function decodeQueryData(bin)
local msgSubType = bin[10]
local statusType = bin[11]
if msgSubType == 0x32 then
if statusType == 0x01 then
return decodeQueryWorkStatusBin(bin)
elseif statusType == 0x05 then
return decodeQueryDisturbBin(bin)
end
elseif msgSubType == 0x34 then
if statusType == 0x01 then
return decodeQueryObserverBin(bin)
end
elseif msgSubType == 0x35 then
return decodeQueryParts(bin)
end
return ""
end
local function decodeRunStatusData(bin)
local msgSubType = bin[10]
if msgSubType == 0x42 then
end
local workStatus = bin[11]
local controlType = bin[13]
local movement = bin[14]
local cleanMode = bin[15]
local fanLevel = bin[16]
local area = bin[17]
local waterLevel = bin[18]
local voiceVolume = bin[19]
local reserveWeekday = bin[20]
local batteryRatio = bin[21]
local workMin = bin[22]
local statusSummaryLow = bin[23]
local errUserLow = bin[24]
local errUserMid = bin[25]
local mop = bin[26]
local query = {}
query["version"] = VERSION
query["work_status"] = getCodeStr(WORK_STATUS_CODE, workStatus)
for i = 0, 7 do
local key = getCodeStr(STATUS_SUM_CODE, i)
local value = bitAt(statusSummaryLow, i)
if key ~= nil and key ~= "none" then
query[key] = value == true and "yes" or "no"
end
key = getCodeStr(USER_LOW_CODE, i)
value = bitAt(errUserLow, i)
if key ~= nil and key ~= "none" then
query[key] = value == true and "yes" or "no"
end
key = getCodeStr(USER_MID_CODE, i)
value = bitAt(errUserMid, i)
if key ~= nil and key ~= "none" then
query[key] = value == true and "yes" or "no"
end
end
query["control_type"] = getCodeStr(CONTROL_TYPE_CODE, controlType)
query["move_direction"] = getCodeStr(MOVEMENT_CODE, movement)
query["work_mode"] = getCodeStr(CLEAN_MODE_CODE, cleanMode)
query["fan_level"] = getCodeStr(FAN_LEVEL_CODE, fanLevel)
query["area"] = tostring(area)
query["water_level"] = getCodeStr(WATER_LEVEL_CODE, waterLevel)
query["voice_level"] = tostring(voiceVolume)
query["have_reserve_task"] = reserveWeekday == 0 and "yes" or "no"
query["battery_percent"] = tostring(batteryRatio)
query["work_time"] = tostring(workMin)
query["mop"] = mop == 0x1 and "yes" or "no"
return wrapTableToJson("status", query)
end
local function decodeSumData(bin)
local msgSubType = bin[10]
local sumType = bin[11]
if msgSubType == 0x66 and sumType == 0x01 then
local sideBrushRestTime = bit.lshift(bin[13], 8) + bin[12]
local sideBrushLifeTime = bit.lshift(bin[15], 8) + bin[14]
local filterNetRestTime = bit.lshift(bin[17], 8) + bin[16]
local filterNetLifeTime = bit.lshift(bin[19], 8) + bin[18]
local rollBrushRestTime = bit.lshift(bin[21], 8) + bin[20]
local rollBrushLifeTime = bit.lshift(bin[23], 8) + bin[22]
local query = {}
query["side_brush_rest_time"] = tostring(sideBrushRestTime)
query["side_brush_life_time"] = tostring(sideBrushLifeTime)
query["filt_brNet_rest_time"] = tostring(filterNetRestTime)
query["filt_brNet_life_time"] = tostring(filterNetLifeTime)
query["roll_brush_rest_time"] = tostring(rollBrushRestTime)
query["roll_brush_life_time"] = tostring(rollBrushLifeTime)
return wrapTableToJson("status", query)
end
if msgSubType == 0x66 and sumType == 0x03 then
local area = bin[12]
local workMin = bin[13]
local cleanMode = bin[14]
local query = {}
query["area"] = tostring(area)
query["work_mode"] = getCodeStr(CLEAN_MODE_CODE, cleanMode)
query["work_time"] = tostring(workMin)
return wrapTableToJson("status", query)
end
return nil
end
local function decodeErrorReport(bin)
local msgSubType = bin[10]
local infra_red_low = bin[11] or 0
local infra_red_high = bin[12] or 0
local failure_low = bin[13] or 0
local failure_mid = bin[14] or 0
local failure_high = bin[15] or 0
local user_info_low = bin[16] or 0
local user_info_mid = bin[17] or 0
local e = {}
if msgSubType == 0xA1 then
for i = 0, 7 do
local key = getCodeStr(ERR_INFRA_RED_LOW_CODE, i)
local value = bitAt(infra_red_low, i)
if key ~= nil and key ~= "none" then
e[key] = value == true and "yes" or "no"
end
key = getCodeStr(ERR_INFRA_RED_HIGH_CODE, i)
value = bitAt(infra_red_high, i)
if key ~= nil and key ~= "none" then
e[key] = value == true and "yes" or "no"
end
key = getCodeStr(ERR_FAILURE_LOW_CODE, i)
value = bitAt(failure_low, i)
if key ~= nil and key ~= "none" then
e[key] = value == true and "yes" or "no"
end
key = getCodeStr(ERR_FAILURE_MID_CODE, i)
value = bitAt(failure_mid, i)
if key ~= nil and key ~= "none" then
e[key] = value == true and "yes" or "no"
end
key = getCodeStr(ERR_FAILURE_HIGH_CODE, i)
value = bitAt(failure_high, i)
if key ~= nil and key ~= "none" then
e[key] = value == true and "yes" or "no"
end
key = getCodeStr(ERR_USER_LOW_CODE, i)
value = bitAt(user_info_low, i)
if key ~= nil and key ~= "none" then
e[key] = value == true and "yes" or "no"
end
key = getCodeStr(ERR_USER_MID_CODE, i)
value = bitAt(user_info_mid, i)
if key ~= nil and key ~= "none" then
e[key] = value == true and "yes" or "no"
end
end
if bitAt(infra_red_low, 0) and bitAt(infra_red_low, 1) then
e["infra_red_low_right_collision"] = nil
e["infra_red_low_left_collision"] = nil
e["infra_red_low_center_collision"] = "yes"
end
end
return wrapTableToJson("status", e)
end
local function checkJsonData(j)
local msg = j["msg"]
if not msg then
return false
end
local data = msg["data"]
if not data then
return false
end
return rst
end
local function checkBinSum(bin)
local msgLen = bin[1]
if msgLen > #bin then
dLog("msgLen no valid")
return false
end
if bin[0] ~= 0xaa then
rst = false
end
if bin[2] ~= 0xb8 then
rst = false
end
local realSum = makeSum(bin, msgLen - 1)
if bin[msgLen] ~= realSum then
dLog("check sum valid")
return false
end
return true
end
function dataToJson(jsonStr)
if #jsonStr == 0 then
return nil
end
local j = JSON.decode(jsonStr)
if checkValidJson(j) == false then
return nil
end
local data = j["msg"]["data"]
data = string.gsub(data, ",", "")
local bin = convertStringToInt(data)
if checkBinSum(bin) == false or checkJsonData(bin) then
return nil
end
local msgType = bin[9]
if msgType == 0x02 then
return decodeControlData(bin)
elseif msgType == 0x03 then
return decodeQueryData(bin)
elseif msgType == 0x04 then
return decodeRunStatusData(bin)
elseif msgType == 0x06 then
return decodeSumData(bin)
elseif msgType == 0x0A then
return decodeErrorReport(bin)
end
return nil
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment