xiaomi_cooker
xiaomi_cooker copied to clipboard
Editing Cooker Profiles
Hello, do you know the format of profile property in cooker_profile.json? Is it possible to decode it and make my own custom cooking profile?
Thank you.
__d(function (global, _$$_REQUIRE, _$$_IMPORT_DEFAULT, _$$_IMPORT_ALL, module, exports, _dependencyMap) {
var _interopRequireDefault = _$$_REQUIRE(_dependencyMap[0]);
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = undefined;
var _classCallCheck2 = _interopRequireDefault(_$$_REQUIRE(_dependencyMap[1]));
var _createClass2 = _interopRequireDefault(_$$_REQUIRE(_dependencyMap[2]));
var _reactNative = _$$_REQUIRE(_dependencyMap[3]);
var CookProfile = function () {
function CookProfile() {
(0, _classCallCheck2.default)(this, CookProfile);
this._mergeByte = function (b1, b2) {
var i = b1;
i = i << 8;
return i | b2 & 0xFF;
};
}
(0, _createClass2.default)(CookProfile, [{
key: "formData",
value: function formData(realData) {
if (realData === undefined || realData === null || realData === '') {
return;
}
if (realData.length < 25) return null;
var rdata = new Array();
var j = 0;
for (var i = 0; i < realData.length; i += 2) {
var sh = realData.substring(i, i + 2);
rdata[j++] = Number.parseInt(sh, 16) & 0xFF;
}
var cookProfile = new CookProfile();
cookProfile.data = rdata;
return cookProfile;
}
}, {
key: "toHexData",
value: function toHexData() {
var crc16 = this.calcrc(this.data, this.data.length - 2);
this.data[this.data.length - 2] = crc16 >> 8 & 0xFF;
this.data[this.data.length - 1] = crc16 & 0xFF;
var sb = '';
for (var i = 0; i < this.data.length; i++) {
var dh = (this.data[i] & 0xFF).toString(16);
if (dh.length < 2) {
dh = '0' + dh;
}
sb = sb.concat(dh);
}
return sb.toString();
}
}, {
key: "isCanDisplayLocation",
value: function isCanDisplayLocation() {
return this.getId() == 1 || this.getId() == 2;
}
}, {
key: "isCanChooseRice",
value: function isCanChooseRice() {
return this.getId() <= 2;
}
}, {
key: "isCanConfigTaste",
value: function isCanConfigTaste() {
return this.getId() == 1;
}
}, {
key: "isCanSetTime",
value: function isCanSetTime() {
return this.data[5] != this.data[7] || this.data[6] != this.data[8];
}
}, {
key: "calcrc",
value: function calcrc(tData, count) {
var crc = 0;
var j = 0;
while (--count >= 0) {
crc = crc ^ tData[j++].toString(10) << 8;
for (var i = 0; i < 8; ++i) {
if ((crc & 0x8000) != 0) {
crc = crc << 1 ^ 0x1021;
} else {
crc <<= 1;
}
}
}
return crc & 0xFFFF;
}
}, {
key: "getType",
value: function getType() {
return this.data[0];
}
}, {
key: "getIndex",
value: function getIndex() {
return this.data[2];
}
}, {
key: "setIndex",
value: function setIndex(index) {
this.data[2] = index;
}
}, {
key: "getId",
value: function getId() {
return this._mergeByte(this.data[0], this.data[1]);
}
}, {
key: "setId",
value: function setId(recipeId) {
this.data[0] = recipeId >> 8 & 0xFF;
this.data[1] = recipeId & 0xFF;
}
}, {
key: "setFavorite",
value: function setFavorite(isFavorite) {
if (isFavorite) this.data[2] = this.data[2] | 0x80;else this.data[2] = this.data[2] & 0x7F;
}
}, {
key: "isFavorite",
value: function isFavorite() {
return (this.data[2] & 0x80) != 0;
}
}, {
key: "canFavorite",
value: function canFavorite() {
return this.getId() > 4;
}
}, {
key: "isScheduleEnabled",
value: function isScheduleEnabled() {
return (this.data[2] & 0x40) != 0;
}
}, {
key: "isCanAutoKeepWarm",
value: function isCanAutoKeepWarm() {
return (this.data[2] & 0x20) != 0;
}
}, {
key: "getDuration",
value: function getDuration() {
return this.data[3] * 60 + this.data[4];
}
}, {
key: "setDuration",
value: function setDuration(duration) {
this.data[3] = Math.floor(duration / 60);
this.data[4] = duration % 60;
}
}, {
key: "getDurationMax",
value: function getDurationMax() {
return this.data[5] * 60 + this.data[6];
}
}, {
key: "getDurationMaxHour",
value: function getDurationMaxHour() {
return this.data[5];
}
}, {
key: "getDurationMaxMinute",
value: function getDurationMaxMinute() {
return this.data[6];
}
}, {
key: "getDurationMin",
value: function getDurationMin() {
return this.data[7] * 60 + this.data[8];
}
}, {
key: "getDurationMinHour",
value: function getDurationMinHour() {
return this.data[7];
}
}, {
key: "getDurationMinMinute",
value: function getDurationMinMinute() {
return this.data[8];
}
}, {
key: "setScheduleEnabled",
value: function setScheduleEnabled(enabled) {
if (enabled) {
this.data[9] |= 0x80;
} else {
this.data[9] &= 0x7F;
}
}
}, {
key: "setScheduleDuration",
value: function setScheduleDuration(schedule) {
var scheduleFlag = this.data[9] & 0x80;
this.data[9] = schedule / 60 & 0xFF;
this.data[9] |= scheduleFlag;
this.data[10] = (schedule % 60 | this.data[10] & 0x80) & 0xFF;
}
}, {
key: "getTasteId",
value: function getTasteId() {
return this.data[7];
}
}, {
key: "setTasteId",
value: function setTasteId(tasteId) {
this.data[7] = tasteId & 0xFF;
}
}, {
key: "setCanAutoKeepWarm",
value: function setCanAutoKeepWarm(canAutoKeepWarm) {
if (canAutoKeepWarm) this.data[10] = this.data[10] | 0x80;else this.data[10] = this.data[10] & 0x7F;
}
}, {
key: "isAutoKeepWarmOpened",
value: function isAutoKeepWarmOpened() {
return (this.data[10] & 0x80) == 0x80;
}
}]);
return CookProfile;
}();
exports.default = CookProfile;
},10322,[14305,14320,14323,10033]);
@syssi Thanks a lot for you work on this integration. It looks like this data is from the mi home app, and it unfortunately covers only the first 10 or so bytes + 2 bytes crc out of 242 bytes. Any idea on what the rest does?
Take a look at this PR: https://github.com/rytilahti/python-miio/pull/832
I assume it implements everything you are looking for.
Thanks for the hint. Looks interesting. I wonder if the format of ihcooker profiles differs much from what we have here.
To answer your first question: If I remember correctly most of the recipe is a time series of target temperatures. I don't know the resolution.
Thanks for all the hints, I think I got the very basic profile editing (time and max time only) with a dumb python script. This pretty much covers most of typical uses, although it lacks more fine-grained tuning. I'm posting my script here.
import sys
import crc16
class CookerProfile():
data = bytearray()
def __init__(self, prof : str):
self.load(prof)
def load(self, profile):
while len(profile):
self.data = self.data + int(profile[:2],16).to_bytes(1,sys.byteorder)
profile = profile[2:]
def fixcrc(self):
crc = crc16.crc16xmodem(bytes(self.data[0:-2]))
self.data[-1] = (crc & 0xff)
self.data[-2] = ((crc >> 8) & 0xff)
def duration(self, duration_minutes = None):
if duration_minutes != None:
self.data[3] = int(duration_minutes / 60)
self.data[4] = duration_minutes % 60
d = self.data[3] * 60 + self.data[4]
print(f"duration: {self.data[3]} hrs {self.data[4]} min ({d} min)")
def duration_max(self, duration_minutes = None):
if duration_minutes != None:
self.data[5] = int(duration_minutes / 60)
self.data[6] = duration_minutes % 60
d = self.data[5] * 60 + self.data[6]
print(f"max duration: {self.data[5]} hrs {self.data[6]} min ({d} min)")
def dump(self):
pr = ''.join('{:02x}'.format(x) for x in self.data)
print(pr)
pdata = "0003E2011E040000280080000190551C0601001E00000000000001B8551C0601002300000000000001E0561C0600002E000000000000FFFF571C0600003000000000000000280A0082001E914E730E01001E82FF736E0610FF756E02690A1E75826E0269100F75826E0269100069005A0000000000000000CB"
cook = CookerProfile(pdata)
cook.duration()
cook.duration(10)
cook.duration_max()
cook.fixcrc()
cook.dump()
I wonder if it's possible to add profile selection + time adjustment to the integration itself.