Dishwasher - Candy simply FI - CDI 6015 WiFi

And there are some url errors in the postman template ({{CANDY_BASE_URI}}api/v1/something instead of {{CANDY_BASE_URI}}something because CANDY_BASE_URI includes api/v1/)

I cannot test right now, but it should be "{{ value_json['statusLavatrice']['DryT'] }}"

1 Like

I’ve got this slide from candy support. Some models will be switched to hOn during this year

1 Like

Hello, i share my python code . It works with my washing machine. It return remaining time :

# Candy SimplyFI 
import json
import urllib.request

candy_reponse = urllib.request.urlopen("http://192.168.1.22/http-read.json\?encrypted\=1").read().decode()
#candy_reponse = '7B0D0A09227374617475734C6176617472696365223A7B0D0A09092257694669537461747573223A2231222C0D0A090922457272223A22323535222C0D0A0909224D6163684D64223A2231222C0D0A0909225072223A223135222C0D0A09092250725068223A2230222C0D0A090922534C6576656C223A2232222C0D0A09092254656D70223A223930222C0D0A0909225370696E5370223A2234222C0D0A0909224F707431223A2230222C0D0A0909224F707432223A2230222C0D0A0909224F707433223A2230222C0D0A0909224F707434223A2230222C0D0A0909224F707435223A2230222C0D0A0909224F707436223A2230222C0D0A0909224F707437223A2230222C0D0A0909224F707438223A2230222C0D0A090922537465616D223A2230222C0D0A09092244727954223A2230222C0D0A09092244656C56616C223A22323535222C0D0A09092252656D54696D65223A22313336222C0D0A0909225265636970654964223A2230222C0D0A090922436865636B55705374617465223A2230220D0A097D0D0A7D'
#print(candy_reponse)

candy_json = bytes.fromhex(candy_reponse).decode()
#print(candy_json)
candy_str = str(candy_json)
candy_data = json.loads(candy_str)
statusLavatrice = candy_data["statusLavatrice"]
#print(statusLavatrice)
RemTime = statusLavatrice["RemTime"]
print(RemTime)
2 Likes

If some one is interesetd i have reveresed the bluetooth enrolement procedure.
I have tested it with a candy rapido washing machine and i was able to connect it to my Wi-Fi.
This is the java code that i had take from the android application.

public class SimplpyFiBle {
    
    private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
    private static final int POLYNOMIAL = 25443;
    private static final int SECURITY_WEP = 2;
    private static final int SECURITY_WPA = 3;
    
    public static final byte[] intToByteArray(int i) {
        return new byte[]{(byte) (i >>> 8), (byte) i};
    }
    
    public static String bytesToHex(byte[] bytes) {
        String result = "";
        for (int j = 0; j < bytes.length; j++) {
            int v = bytes[j] & 0xFF;
            result += HEX_ARRAY[v >>> 4];
            result += HEX_ARRAY[v & 0x0F];
        }
        return result;
    }
    
    public static int crc16(byte[] bArr) {
        int i = 65535;
        for (byte b : bArr) {
            i ^= b & 255;
            for (int i2 = 0; i2 < 8; i2++) {
                i = (i & 1) != 0 ? (i >>> 1) ^ POLYNOMIAL : i >>> 1;
            }
        }
        return (~i) & 65535;
    }
    
    public static void appendCRC(byte[] bArr, int i) {
        int length = (bArr.length - i) - 2;
        byte[] bArr2 = new byte[length];
        System.arraycopy(bArr, i, bArr2, 0, length);
        byte[] intToByteArray = intToByteArray(crc16(bArr2));
        int length2 = bArr.length - 2;
        bArr[length2] = intToByteArray[0];
        bArr[length2 + 1] = intToByteArray[1];
    }
    
     public static void main(String []args){
        String ssid = "WIFI_SSID";
        String pass = "WIFI_PASSWORD";
        int security = SECURITY_WPA;
        String key = "HOMEASSISTANTKEY";
        
        if (key.length() != 16) {
            System.out.println("Key length must be 16, current is: " + key.length());
            return;
        }
        
        byte[] bytes = String.format("NTW_MODE=%s&SSID=%s&PASSWORD=%s&ENCRYPT_KEY=%s", Integer.valueOf(security), ssid, pass, key).getBytes();
        int length = bytes.length + 3 + 2;
        byte[] bArr = new byte[length];
        bArr[0] = -91;
        bArr[1] = (byte) length;
        bArr[2] = 22;
        System.arraycopy(bytes, 0, bArr, 3, bytes.length);
        appendCRC(bArr, 0);
        System.out.println(bytesToHex(bArr));
     }
}

This will produce an string that can be used with this app https://play.google.com/store/apps/details?id=no.nordicsemi.android.mcp&hl=en&gl=US
to configure the device.

The steps to configure the device are:

  • Put the device into ble mode
  • Generate the hex string, you can go here: https://www.tutorialspoint.com/compile_java_online.php to run the java code online.
  • Open the nrf connect app
  • Perform a ble scan
  • Connect to the device, you should see a service with UUID 0xA002 click on it
  • On the characteristic with UUID 0xC302 perform a write and select as value type “BYTEARRAY” and past the generated string from the priouse step as value
  • Now your device should be connecting to your Wi-Fi.

I’d like to share my current config:
I use a local polling of the washer with an automation: while the washer is responsive on the network (ping state on), system is waiting for MachMd attribute switch from 2 to 7.
scan_interval values could be more aggressive, I agree, anyway for me it’s ok to get the notification with some minutes delay.
I’m not satisfied about the command line sensor, now checked fulltime despite the washer is off - nice the thread about disabling a sensor - any hint about it?

configuration.yaml file:

binary_sensor:
  - platform: ping
    host: 192.168.202.156
    name: "Candy"
    count: 2
    scan_interval: 30

sensor:
  - platform: command_line
    name: Candy MachMd
    command: "curl -s http://192.168.202.156/http-read.json?encrypted=0 | jq .statusLavatrice.MachMd"
    scan_interval: 30
    command_timeout: 5
    value_template: '{{ value | replace("\"", "") | int }}'

automations.yaml file:

- id: '1612013181362'
  alias: Candy program notifier
  description: ''
  trigger:
  - platform: state
    entity_id: sensor.candy_machmd
    from: '2'
    to: '7'
  condition:
  - condition: state
    entity_id: binary_sensor.candy
    state: 'on'
  action:
  - service: persistent_notification.create
    data:
      message: '{{ now().strftime("%H:%M %Y-%m-%d") }}: Program finished!'
      title: Candy
  mode: single
1 Like

Is there any option to connect all commands in one? At this point there are too many asks from my IP and and the sensors are offline at 70% of time.

You can use attribute_templates to make the attribute of the same sensor. And then you can transform the attributes into sensors with template sensors.

With my washing machine, it start to fail after some time even with only one request per minutes

Watching with intent as I’m just starting to plan a new kitchen and Candy WiFi ovens are half the price of others so of interest!

1 Like

Thank you so much for sharing your configuration.I had to do some syntax adjustments to the MachMd sensor but it work. Namely:

  1. adding escaped quotes to the URL queried by curl

command: "curl -s \"http://192.168.202.156/http-read.json?encrypted=0\" | jq .statusLavatrice.MachMd"

  1. removing the cast to int at the end of value_template
    value_template: '{{ value | replace("\"", "") }}'

Unfortunately for me it’s too hit and miss. It could be that the timing needs adjusting but I suspect that the culprit is the curl script that does not always answer. I monitor the state from the terminal when I’m debugging and I don’t always get an answer.

I still don’t get why the automation is not triggered, maybe when the curl request fails the sensor state becomes undefined or blank therefore there is no 2 -> 7 transition but 2 -> ? -> 7

Maybe the template needs to default to “2” in case of timeout?

I’m debugging this with 2 -> 3 (Paused) and the automation is triggered roughly 50% of the times.

I would like to fine-tune your configuration because it basically works.

Otherwise I’d have to resort to something more reliable but a lot more complicated, i.e. sniffing the packet that the machine sends to the Candy Cloud when it’s finished.

I already did that, if someone’s interested:

<machine_ip> -> 52.213.231.49 HTTP

[truncated]GET /api/av1/listen.json?macAddress=XXXXXXXXXXX&encrypted=0&macAddress=XXXXXXXXXXXX&WiFiStatus=0&Err=0&MachMd=7&Pr=1&PrPh=0&PrCode=0&SLevel=0&Temp=40&SpinSp=10&Opt1=0&Opt2=0&Opt3=0&Opt4=0&Opt5=0&Opt6=0&Opt7=0&Opt8=0&Opt9=0&St
Host: simplyfimgmt.candy-hoover.com

This however is way more complicated and probably not even doable if you don’t have the proper hardware. Your router needs to be able to run some kind of script and trigger some other mechanism that Home Assistant can read reliably.

***** EDIT ***** 16/03/2021
I think I got this to work by lowering the polling frequency of the sensor and increasing the timeout. I also got rid of the binary sensor and I integrated the ping in the command sensor. Here’s my code:

sensor:
  - platform: command_line
    name: Candy MachMd
    command: "ip=192.168.1.10; ping $ip -c 1 -W 2 > /dev/null; if [ $? -eq 0 ]; then res=$(curl -s \"http://$ip/http-read.json?encrypted=0\"); if [ $? -eq 0 ]; then echo $res | jq .statusLavatrice.MachMd; else echo \"\"999\"\"; fi; else echo \"\"0\"\"; fi"
    scan_interval: 60
    command_timeout: 29
    value_template: '{{ value | replace("\"", "") }}'

The readability is not great but basically it works like this:
no ping response -> 0
ping response but empty or no curl response -> 999
ping response, curl response -> value of MachMd

This way I figured out that during wash I was getting 2 / 999 / 2 / 999 / 2 / 999 / 7 / 999 / 7 / 999

I was polling every 30 seconds with 10 seconds timeout. Now I interrogate the machine every 60 seconds and I wait 29 seconds before timeout. I only tried it once so far but I got no 999 and the automation worked.

Hi I don’t have Home Assistant, but have been working on decoding the various values for a Candy Simplyfi Washer Dryer so I can have a status update within my Loxone home automation system.
So this is what I have decoded so far;
MachMd: 1 = Not Yet Started, 2= Program Running, 3= Paused, 5= Delayed Start, 7= Finished

Pr: 1= Special 39’, 2= Perfect Cotton 59’, 3= Mixed and Coloured 59’, 5=Rapid 30’, 11= Bed Linen, 13= Low Dry Heat, 17= Hygiene 60º

PrPh This is program phase 2= Washing I guess 1 would equal PreWash but its only a guess.

Temp is the washing temperature in ºC

SpinSp is Spin Speed you need to multiply by 100 so a value of 8 means 800rpm

RemTime is remaining time is seconds, its not 100% accurate but good enough

DelVal is delayed start time I think this is in minutes

One other thing I have found out is that it uses some form of ESP module for its connectivity, which one I don’t know.
Hope this helps
And this is what it looks like in Loxone

2 Likes

I’m trying to get this to work on my Hoover dishwasher, but I’m a little stuck with it. Here’s what I’m doing …

curl -s http://[DISHWASHERIP]/http-read.json?encrypted=1 | xxd -r -p > ./crypted.txt
Result:

13616F684E1A170A13111222320318064A561E6C66606A49341000120A350228014E5F435D4B4F666D6D6844260D0F070B0920131E06110E455E43235540476362656C433F1D021F08203607160A49544A5E474D61636A624529041204210A1C010F0A43564B53494B696B6F6C40381A091E1132180613495D465144496F6167614E311309000D3E090B435C4752494265666C684E2C0004455E4356474E6664616547311E0604190609435C4732594C44616F68654B261313160022171B49544A5C474D61636A62452B11030B2604011A2315154E53415B45486C6C6C6B492A0D0004183F1D021913465B445540476362656C433E0C0E3F0E0904445F405A5D584E496C66606A492A0D12153603071A4A5647504E456E616E6D432B0C11183C010216044E53415B45486C6C6C6B492118090B25030611495D465144496F6167614E37041F0C17495D465144496F6167614E2609090A083E17465B445540476362656C431E5841514555434A686862674A1E5743564B53494B696B6F6C40195D4A5647534E456E616E6D43145140514C594E496C66606A491551435C4752494265666C684E1B55495D465044496F6167614E17564E53415845486C6C6C6B491C504E5F435C4B4F666D6D6844175B49544A22302D204B4F666D6D684417535E4C524E544361636A166A6E1C

./xorknown.py ./crypted.txt '{"statusDWash' 16

returns no results.

Thanks.

For the value of PrPh:

  • 1: prewash
  • 2: wash
  • 3: rinse
  • 4: last rinse
  • 5: end (never seen it)
  • 6: drying
  • 7: error mode
  • 8: steam
  • 9: good night (no idea what this mean)

I also get the value 10 with my machine between the last rinse and drying stage

It seems that RemTime is always a multiple of 60. For me, this is value match on the device screen, but this seems to be very inaccurate, but this may be because the drying time is automatic and thus not really predictable.

Your response is formatted slightly differently. By using various shorter known plain text you can find more about your response until you find enough to recover the whole key.

If you don’t want to use the official app, a simpler solution is to reconfigure your appliance with a known key (search http-config.json in this thread).

For your case here is the result:

$ ./xorknown.py /tmp/bar '"StatoWiFi":"1",' 16
Searching XOR-encrypted /tmp/bar for string '"StatoWiFi":"1",' (max_key_length = 16)
Key length: 16 
Partial Key: hlealickgdafebkn 
Plaintext: {
        "statusDWash":{
                "StatoWiFi":"1",
                "CodiceErrore":"E0",
                "StatoDWash":"2",
                "MetaCarico":"0",
                "StartStop":"0",
                "TreinUno":"0",
                "Eco":"0",
                "Program":"P2",
                "ExtraDry":"0",
                "OpenDoorOpt":"0",
                "DelayStart":"0",
                "RemTime":"130",
                "MissSalt":"1",
                "MissRinse":"0",
                "OpenDoor":"0",
                "Reset":"0",
                "CheckUp":"0",
                "r1":"1",
                "r2":"0",
                "r3":"2",
                "r4":"1",
                "r5":"0",
                "r6":"1",
                "r7":"3",
                "r8":"0",
                "r9":"NULL",
                "r15":"1"
        }
}

A big thanks for that @dzamlo, now I just have to create a sensor or 2 :slight_smile:

PrPh value of 10 = Spin

Does any one have a full set of attribute and sensor templates to bring all of the data in to Home Assistant?

I’ve got the decrypted JSON from my new washer/dryer but am not sure of the best way to bring this all in to home assistant in one go.

Thanks

Hi everyone, I’m seeing a great job of integrating Candy / Hoover appliances into Home Assistant. I can watch in admiration as my skills stop much earlier.
I would like to ask you if one day it will also be possible to control pause / stop / restarting during a wash via Home Assistant.
Thank you

Hi @dzamlo, thanks for the help so far :slight_smile:

I’ve downloaded your script and would like to test it using a text file, could you please possibly convert the following lines to read a text file that resides in my appdeamon/apps/ folder called hotpoint.txt

        res = requests.get(
        "http://" + appliance_ip + "/http-" + command + ".json?encrypted=1",
        timeout=request_timeout,
        return json.loads(self.decrypt(codecs.decode(res.text, "hex"), encryption_key))
        def decrypt(self, cipher_text, key):

Oh, the file will consist of this:

13616F684E1A170A13111222320318064A561E6C66606A49341000120A350228014E5F435D4B4F666D6D6844260D0F070B0920131E06110E455E43235540476362656C433F1D021F08203607160A49544A5E474D61636A624529041204210A1C010F0A43564B53494B696B6F6C40381A091E1132180613495D465144496F6167614E311309000D3E090B435C4752494265666C684E2C0004455E4356474E6664616547311E0604190609435C4732594C44616F68654B261313160022171B49544A5C474D61636A62452B11030B2604011A2315154E53415B45486C6C6C6B492A0D0004183F1D021913465B445540476362656C433E0C0E3F0E0904445F405A5D584E496C66606A492A0D12153603071A4A5647504E456E616E6D432B0C11183C010216044E53415B45486C6C6C6B492118090B25030611495D465144496F6167614E37041F0C17495D465144496F6167614E2609090A083E17465B445540476362656C431E5841514555434A686862674A1E5743564B53494B696B6F6C40195D4A5647534E456E616E6D43145140514C594E496C66606A491551435C4752494265666C684E1B55495D465044496F6167614E17564E53415845486C6C6C6B491C504E5F435C4B4F666D6D6844175B49544A22302D204B4F666D6D684417535E4C524E544361636A166A6E1C

which I get by going to this address:

http://192.168.0.180/http-read.json?encrypted=1

I know it’s a bit of an ask, but I’ll study it and see where I’ve been going wrong for the past few hours :confused:

Sorted, took hours, but sorted.

    def get_data(self, command):
    with open('//config/appdaemon/apps/hotpoint.txt', 'r') as file:
        data = file.read().replace('\n', '')
    return json.loads(self.decrypt(codecs.decode(data, "hex"), encryption_key))

I’m not expert, but I can’t see that happening for a while, if ever, as we’re only reading info at the moment.