Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Editing Cooker Profiles #15

Open
strup8 opened this issue Jan 17, 2020 · 6 comments
Open

Editing Cooker Profiles #15

strup8 opened this issue Jan 17, 2020 · 6 comments
Labels
enhancement New feature or request

Comments

@strup8
Copy link

strup8 commented Jan 17, 2020

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.

@syssi
Copy link
Owner

syssi commented Oct 30, 2020

__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 syssi added the enhancement New feature or request label Nov 1, 2020
@nekromant
Copy link

@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?

@syssi
Copy link
Owner

syssi commented May 24, 2021

Take a look at this PR: rytilahti/python-miio#832

I assume it implements everything you are looking for.

@nekromant
Copy link

Thanks for the hint. Looks interesting. I wonder if the format of ihcooker profiles differs much from what we have here.

@syssi
Copy link
Owner

syssi commented May 24, 2021

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.

@nekromant
Copy link

nekromant commented May 26, 2021

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants