Koenkk / zigbee2mqtt

Zigbee 🐝 to MQTT bridge πŸŒ‰, get rid of your proprietary Zigbee bridges πŸ”¨
https://www.zigbee2mqtt.io
GNU General Public License v3.0
11.73k stars 1.64k forks source link

[New device support]: Bosch Outdoor Siren #13520

Closed safakaltun closed 1 year ago

safakaltun commented 2 years ago

Link

https://www.bosch-smarthome.com/uk/en/products/devices/outdoor-siren/

Database entry

{"id":66,"type":"Unknown","ieeeAddr":"0x003c84fffeeda00b","nwkAddr":29598,"epList":[],"endpoints":{},"interviewCompleted":false,"meta":{},"lastSeen":1660330204622,"defaultSendRequestWhen":"immediate"}

Comments

I would like to add support for this device, but I got stuck in the pairing phase. Although the device starts pairing, it cannot complete the interview. Pairing with the original hub was done using the QR code on the back of the siren, which contains the IEEE link key. I am not sure if it has anything to do with it or not.

I factory reset the device numerous times with no help. I also tried pairing it in both legacy and "smart home controller" modes which is set by the PIN 8 behind the siren.

Attaching the herdsman logs as well. I am not sure if the length of the log is enough, but the device more or less runs in a loop where z2m tries interviewing, interview fails and starts over again.

herdsman.log

External converter

No response

Supported color modes

No response

Color temperature range

No response

danieledwardgeorgehitchcock commented 1 year ago

@safakaltun - I experience similar behaviour. I haven't found a timeout or a reset command that sets it back to what is required yet though...

github-actions[bot] commented 1 year ago

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 7 days

safakaltun commented 1 year ago

Good news! I have finally managed to get the transport key from the bosch smart home controller through other bosch devices (https://github.com/Koenkk/zigbee2mqtt/issues/14355). I will sniff the traffic over the weekend and try to understand how tamper is implemented.

danieledwardgeorgehitchcock commented 1 year ago

Any chance you could share it so I can help you out? - The transport (network) key should be the same for all devices on your Bosch network and hopefully, the same key is shared across all hubs..

safakaltun commented 1 year ago

As far as I know, the transport key is initially randomly generated by the Bosch Smart Controller (BSC). So that will be device specific. However, I can try describing what I did to get the transport key.

1- I updated my Wireshark to the most recent version which is 4.0.1. 2- After that, I needed to find the zigbee channel BSC use. I noticed that after connecting the first device, it tries to find the best channel and switches to that channel. 3- Add only the installation key as the pre-configured key. The install key should also include the the 2 bit CRC. So, in this case, it should be 18 bits long. 4 - Initiate pairing

For me, these steps revealed the transport key that is used by the BSC.

danieledwardgeorgehitchcock commented 1 year ago

OK thanks! I'll give it a go.

I remember trying something similar on my siren but didn't get anywhere. Will give it a go with the exact method you described above

safakaltun commented 1 year ago

I tried the steps above to see if I could get the Transport Key using the Install Code of the siren, and it did not work. So it might not work for you either. However, since I already have the Transport Key thanks to the other devices, I did a fairly extensive sniffing while testing the device through the app.

Here is the sniff from the siren. I am not sure if the encryption keys are embedded into the sniff file or not. If not, here is the Transport Key: 79:d3:38:71:27:0f:51:0b:01:20:89:f6:bc:80:eb:3c.

siren_pairing_and_settings_log.zip

Below you can find my findings from the sniff. With respect to tamper, something funny might be happening like the device waiting for a command from the hub to update its value at least for the first time.

Bindings | Source Endpoint | Destination endpoint| Cluster | | 1 | 2 | Cluster: 0x0502 (Intruder Alarm System WD) | | 1 | 2 | Cluster: 0x0500 (Intruder Alarm System Zone) | | 1 | 2 | Cluster: 0x0019 (OTA Upgrade) | | 1 | 2 | Cluster: 0x0020 (Poll Control) | | 1 | 2 | Cluster: 0x0001 (Power Configuration) |

Reporting Configuration


Source Endpoint | Destination Endpoint | Cluster | Attribute | MinRep | MaxRep | | 2 | 1 | Cluster: Power Configuration (0x0001) | Attribute: Unknown (0x003e) | 0 | 65534 | | 2 | 1 | Cluster: Power Configuration (0x0001) | Attribute: Battery Percentage Remaining (0x0021) | 0 | 65534 | | 2 | 1 | Cluster: Power Configuration (0x0001) | Attribute: 0xa002 | 0 | 65534 | | 2 | 1 | Cluster: Power Configuration (0x0001) | Attribute: 0xa000 | 0 | 65534 | | 2 | 1 | Cluster: Intruder Alarm System Zone (0x0500) | Attribute: 0xa001 | 0 | 65534 | | 2 | 1 | Cluster: Power Configuration (0x0001) | Attribute: 0xa001 | 0 | 65534 |


----------- IAS ZONE/ IAS ZONE WD Findings ------------

It enrolls to IAS Zone at no 289. All read/report attributes are done via Cluster: Intruder Alarm System Zone (0x0500)

Write Attributes are done via Cluster: Intruder Alarm System WD (0x0502)


| Action | Attribute | Value range | Wireshark Log No. | | Change default flashing light alarm duration (min) | 0xa005 | | 1120 | | Change default siren alarm duration (min) | 0xa000 | | 1180 | | Siren volume (-) | 0xa002 | 1-low 2-med 3-high | 1225 | | Siren alarm delay (s) | 0xa003 | | 1350 | | Flashing light alarm delay (s) | 0xa004 | | 1384 |

Report Attributes (Cluster: Intruder Alarm System Zone (0x0500))


| Action | Attribute | Value range | Wireshark Log No. | | ZCL IAS Zone: Zone Status Change Notification due to tamper on ->> off | ZoneStatus | ------ | 1708, 1716, 1718 (First it sends the current value with tampered=1, second receives an unknown command from the hub, then sends the updated value tampered=0) | ZCL IAS Zone: Zone Status Change Notification due to tamper off ->> on | ZoneStatus | ------ | 1851, 1859 (Sends the updated value with tamper on (tampered=1), then receives an unknown command from the hub) | ZCL IAS Zone: Zone Status Change Notification when alarm triggered via wired channel | ZoneStatus | ------ | 2420, 2428 (Sends the updated value, then receives an unknown command from the hub) | ZCL IAS Zone: Zone Status Change Notification when alarm deactivated via wired channel | ZoneStatus | ------ | 2555, 2563 (Sends the updated value, then receives an unknown command from the hub)

I am not sure what does the Unknown Command (0xf3) do, but it probably plays a role.

----------- Power Config Findings -------------

Cluster: Power Configuration (0x0001) is used for read/write/report

Write Attributes


| Action | Attribute | Value range | Wireshark Log No. | | Power supply selection | 0xa002 | 2 (5-28v ) | 1438 | | Power supply selection | 0xa002 | 1 (230v ) | 1508 | | Power supply selection | 0xa002 | 0 (solar power) | 1611 |

Report Attributes


| Action | Attribute | Value range | Wireshark Log No. | | Power supply selection | 0xa002 | 0-2 | 1444 | | Battery percentage (0.5%) | 0x0021 | 0-200 | 385 |

danieledwardgeorgehitchcock commented 1 year ago

This is all great stuff! I'll look into it over the weekend. Out of curiosity, what device allowed you to find the backdoor for the transport key?

safakaltun commented 1 year ago

Thanks! It was the Twinguards.

danieledwardgeorgehitchcock commented 1 year ago

I tried with your transport key and it looks like each bosch hub has its own which is unfortunate. I have ordered a used twinguard so I can do my own sniffing but I'm also reviewing the data that you've captured in the above pcap file. Hopefully between us, we can crack it!

safakaltun commented 1 year ago

I think the BSC generates a random key and I remember reading that it also updates that key periodically (don't remember where, I read a lot of stuff about these devices recently :D).

I am sure we will manage! combination of the outdoor siren and twinguard is also nice actually. One can trigger the other. Anyways, I have had a look at what I captured and compared it a new capture between z2m and the siren. I think I know what the problem is, but I have no clue how to fix that through z2m.

So whenever the status changes from "tampered" to "not tampered", the device first sends out a change notification with the existing zoneStatus. Then it seems like it expects a command from the hub to send an updated zonestatus. After receiving the command, it sends a new change notification with tamper status 0. These are the 3 selected entries in the below snippet.

image

On the other hand, when the tamper status changes from "not tampered" to "tampered", the siren sends the change notification with an updated value (tamper: 1). It still receives a message from the hub, but this time it does not update the zonestatus since it is already updated. You can see it below as well:

image

In both cases, the command sent from the hub is the same:

image

So I think if we can solve this, we can solve the problem with the tamper.

danieledwardgeorgehitchcock commented 1 year ago

Hmm. Interesting! This sounds like something I have done before with a yale lock - I will see if I can dig the code out and modify it! Good spot!

It looks as though the missing piece of the puzzle is also a custom ZCL frame (Robert Bosch 0x1209) so, it'd probably be worth creating a custom ZCL frame / cluster similar to what is done with manufacturerSpecificTuya

danieledwardgeorgehitchcock commented 1 year ago

@Koenkk - any ideas how we can send the above frame?

I have tried with the below in a local toZigbee converter but I get an Unsupported Attribute error...

await entity.write('ssIasZone', {0xf3: {value: 2, type: 0x20}}, {manufacturerCode: 0x1209})

safakaltun commented 1 year ago

A learning from Twinguards is that the attribute names are shown as decimals in the payload instead of hexadecimals for some reason. So, perhaps you can try

await entity.write('ssIasZone', {243: {value: 2, type: 0x20}}, {manufacturerCode: 0x1209})

It might be a long shot, but it might work.

danieledwardgeorgehitchcock commented 1 year ago

I can see that Z2M is converting the hex values correctly, but good thought! I just think my payload is probably incorrect but, I have tried finding a converter that may do something similar in Z2M and have come up empty - the above is an adaption of an Aqara cover payload..

danieledwardgeorgehitchcock commented 1 year ago

@safakaltun - I can confirm that I have now successfully sniffed my Transport Key using your Twinguard work around!

My key join key was the last 16 hex pairs of the decoded QR but other than that, everything worked as you described and I can now see the decoded traffic on my Outdoor Siren too!

I will keep trying to work out how to mimic the request from the Bosch Controller - I'll also look at seeing what other functionality I can add to the converter too now that I can decode the traffic :-)

safakaltun commented 1 year ago

Amazing, and it only took us 4 months to figure out how to do that! :)

I will let you know if I find something interesting. It really seems like there is light at the end of the tunnel!

danieledwardgeorgehitchcock commented 1 year ago

Ok,

I have been looking through the sniff of my siren joining and it looks as though it enrols to an IAS Zone - I am thinking that may be why I may be getting some weird errors when trying to replicate the behaviour:

IAS Zone Enrol

I am not sure if this is happening in Z2M...?

safakaltun commented 1 year ago

Perhaps this discussion might help. I cannot check it at the moment, but there might be at least 2 devices in z2m supporting enrollment.

danieledwardgeorgehitchcock commented 1 year ago

I have just done a fresh sniff of the siren joining the Z2M network and can confirm that it is indeed enrolled so, back to fault finding my command!

Koenkk commented 1 year ago

https://github.com/Koenkk/zigbee2mqtt/issues/13520#issuecomment-1337186900

This is a command, not a write command. It needs to be added to https://github.com/Koenkk/zigbee-herdsman/blob/master/src/zcl/definition/cluster.ts before you can send such command.

danieledwardgeorgehitchcock commented 1 year ago

@safakaltun - please see progress being made in the above PR :-)

danieledwardgeorgehitchcock commented 1 year ago

Here is my current converter which sends the command:

const fz = require('zigbee-herdsman-converters/converters/fromZigbee');
const tz = require('zigbee-herdsman-converters/converters/toZigbee');
const exposes = require('zigbee-herdsman-converters/lib/exposes');
const reporting = require('zigbee-herdsman-converters/lib/reporting');
const extend = require('zigbee-herdsman-converters/lib/extend');
const utils = require('zigbee-herdsman-converters/lib/utils');
const e = exposes.presets;
const ea = exposes.access;

const fzLocal = {
    ias_siren: {
        cluster: 'ssIasZone',
        type: 'commandStatusChangeNotification',
        convert: async (model, msg, publish, options, meta) => {
            const zoneStatus = msg.data.zonestatus;
            await msg.endpoint.command('ssIasZone', 'boschTestTamper', {data: 2},{manufacturerCode: 0x1209});
            return {
                alarm: (zoneStatus & 1) > 0,
                tamper: (zoneStatus & 1<<2) > 0,
                battery_low: (zoneStatus & 1<<3) > 0,
                supervision_reports: (zoneStatus & 1<<4) > 0,
                restore_reports: (zoneStatus & 1<<5) > 0,
                ac_status: (zoneStatus & 1<<7) > 0,
                test: (zoneStatus & 1<<8) > 0,
            };
        },
    },
};

const definition = {
    zigbeeModel: ['RBSH-OS-ZB-EU'],
    model: 'BSIR-EZ',
    vendor: 'Bosch',
    description: 'Wired/Wireless Smart Outdoor Siren',
    fromZigbee: [fz.battery, fzLocal.ias_siren],
    toZigbee: [tz.warning],
    exposes: [e.warning(), e.battery(), e.battery_low(), e.tamper()],
    configure: async (device, coordinatorEndpoint, logger) => {
        const endpoint = device.getEndpoint(1);
        await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg', 'ssIasZone']);
        device.defaultSendRequestWhen = 'immediate';
        device.save();
        await endpoint.unbind('genPollCtrl', coordinatorEndpoint);
        },
};

module.exports = definition;

There's still a lot of work required on doing the follow on logic and commands to bring back the correct tamper status zoneStatus seems to be all over the place and zoneState doesn't change after the command

safakaltun commented 1 year ago

@danieledwardgeorgehitchcock thanks a lot! Since you have got this covered, I will focus on the integration of the twinguard. Then perhaps we can validate each other's integrations.

danieledwardgeorgehitchcock commented 1 year ago

I like the sound of that! Happy to help with the twinguard too as I have one of those too now!

safakaltun commented 1 year ago

In the first paragraph of https://github.com/Koenkk/zigbee2mqtt/issues/14355#issuecomment-1347309852 I explained my main problem, which is how/where to change the clusters.ts file. If you can help with that, I believe I can progress quite fast since I already have mapped all the commands that one can generate using the app. :)

Grandma-Betty commented 1 year ago

Folks, I don't get it. It this device supported now or not (yet)? I cannot find it in the device support list but the issue here is closed. Thanks!

danieledwardgeorgehitchcock commented 1 year ago

No,

It's not in the Z2M codebase at present as we are still trying to work out the tamper logic

datenzar commented 1 year ago

Hi @danieledwardgeorgehitchcock and @safakaltun! Thanks for sharing all that information. With your shared code I was able to join my siren to my z2m network.

However I was also curious about sniffing Zigbee and found a way to get the Network Key from the Bosch SHC.

I tried the steps above to see if I could get the Transport Key using the Install Code of the siren, and it did not work. So it might not work for you either. However, since I already have the Transport Key thanks to the other devices, I did a fairly extensive sniffing while testing the device through the app.

@safakaltun If you add the key A9FBAFCA2C2F186222C5A9C839DD124B to your shared Wireshark output, you will get see the network key in package 44 (Zigbee APS). @danieledwardgeorgehitchcock For you, the link key that the device is using would be BF2237BE17FC707A72949EA7960027D8. You will see the network key - you guessed it - in the packet 23.

How did I get above keys? It's mentioned in the docs that the key exchange will take place based on a derived link key. The derivation is actually just the MMO (Matyas-Meyer-Oseas) hash of the install code + crc. See here for a nice illustration: https://wiki.st.com/stm32mcu/nsfr_img_auth.php/thumb/2/24/Conectivity_InstallCode.png/700px-Conectivity_InstallCode.png

nanosonde commented 1 year ago

I see that this issue is closed and there have been commits to zigbee-herdsman with Bosch-specific additions. Is the outdoor siren supported now? I do not find it in the supported devices list.

Grandma-Betty commented 1 year ago

Is the outdoor siren supported now? I do not find it in the supported devices list.

I would like to know as well.

ParalaX002 commented 1 year ago

Hi, any information as if the siren is indeed working, and if yes, any pointer as to where to find the configuration? Thanks!

safakaltun commented 11 months ago

Hi all, this works for me. I am not sure why it is not in the list of supported devices.

danieledwardgeorgehitchcock commented 10 months ago

Hi,

Short answer is I got busy and was also awaiting a change on the Frontend code base to help me streamline the converter's development see: nurikk/zigbee2mqtt-frontend#1701

It looks like a number of other people have contributed to getting this to work so, I don't mind firming this up into some sort of support with a plan on adding functionality at a later date..?

vjonas commented 10 months ago

Hi @danieledwardgeorgehitchcock and @safakaltun! Thanks for sharing all that information. With your shared code I was able to join my siren to my z2m network.

However I was also curious about sniffing Zigbee and found a way to get the Network Key from the Bosch SHC.

I tried the steps above to see if I could get the Transport Key using the Install Code of the siren, and it did not work. So it might not work for you either. However, since I already have the Transport Key thanks to the other devices, I did a fairly extensive sniffing while testing the device through the app.

@safakaltun If you add the key A9FBAFCA2C2F186222C5A9C839DD124B to your shared Wireshark output, you will get see the network key in package 44 (Zigbee APS). @danieledwardgeorgehitchcock For you, the link key that the device is using would be BF2237BE17FC707A72949EA7960027D8. You will see the network key - you guessed it - in the packet 23.

How did I get above keys? It's mentioned in the docs that the key exchange will take place based on a derived link key. The derivation is actually just the MMO (Matyas-Meyer-Oseas) hash of the install code + crc. See here for a nice illustration: https://wiki.st.com/stm32mcu/nsfr_img_auth.php/thumb/2/24/Conectivity_InstallCode.png/700px-Conectivity_InstallCode.png

@datenzar Hey man, could you elaborate a bit more on this? I would like sniff on the bosch controller network too. I got a smoke alarm II to pair with. This is the scanned QR code value: RB01SG0D8310182648007000000000000000000094DEB8FFFE41F6D6DLKA87710C3E5C332E5327EE532C3C310E5DFE0

I figure following details will be extracted: manufacturer identifier: RB01SG

model identifier: 0D83101826480070000000000000000000

MAC adres: 94DEB8FFFE41F6

?:D6DLK

Network key / install code: A87710C3E5C332E5327EE532C3C310E5

CRC: DFE0

I do not have access to the MMO hash functionality. Could you help me out here?

Kr

Jonas

vjonas commented 10 months ago

I figured something out. Use this repo to access the aes-mmo functionality to decrypt the message containing the transport key: https://github.com/andrebdo/c-crumbs/tree/master

and then run the code with your installcode like this. It will give you the hash code to encrypt it:

void hex_string_to_byte_array(const char *hex_string, unsigned char *byte_array, int byte_array_length) {
    for (int i = 0; i < byte_array_length; i++) {
        sscanf(&hex_string[i*2], "%2hhx", &byte_array[i]);
    }
}

int main() {
char* installCode = "A87710C3E5C332E5327EE532C3C310E5DFE0";
    int length = strlen(installCode) / 2; // Each byte is represented by 2 hex characters
    unsigned char* message = malloc(length);
    hex_string_to_byte_array(installCode, message, length);
    char digest[16];
    aes_mmo(digest, message, length);

    for (int i = 0; i < 16; i++) {
        printf("%02x", (unsigned char)digest[i]);
    }
    printf("\n");

    free(message);
    return 0;
}