After looking at a lot of decompiled code things started to make more sense...

AES?

The STM32L100RC doesn't have AES functionality builtin, but the SPIRIT1 does so when I came across an AES implementation contained with the code I was a little surprised. This turns out to be 128-byte AES ECB and there is a default key contained within the code. There is also a key generation function, so which would be used? I'd need to look at the messages to get more of an idea.

The code is from the CycloneCrypto library which makes a lot of sense given it is intended for use with such devices. This discovery made progress much faster.

The use of ECB mode avoids the need for sharing the IV and allows for simpler keys to be used.

The default key is 30:18:8C:C6:D9:EC:F6:FB:E7:F3:F9:FC:C1:60:B0:D8

Every packet has an address byte followed by 4 control bytes. Most packets then contain 19 bytes, the first 3 of which are almost always identical. The decompiled code shows repeated use of the first 7 bytes of a packet during creation, so it now looks like

Address  |  Control Bytes  |  3 Bytes of Header  |  16 bytes of AES encoded data
Probable format of packets being sent

Ignoring the AES encoded bytes for the moment, this looks pretty good.

ff 53 00 11 06 05 ff 00 
ff c6 00 23 06 05 ff 00 
ff fc 00 21 06 05 ff 00 
ff 53 00 12 06 05 ff 00 
ff c6 00 24 06 05 ff 00
ff fc 00 22 06 05 ff 00
ff 53 00 13 06 05 ff 00
ff c6 00 25 06 05 ff 00
ff 53 00 14 06 05 ff 00
ff c6 00 26 06 05 ff 00
First 8 bytes of transmitted packets.

Looking at this, it appears that we have a sequence number at byte 4. I have observed this wrap at 0x3F and indeed the code supports this. The second byte is the address of the device sending the message (I have 3 radiators and 3 different adresses). All the above packets are regular "heartbeats". When the controller is awake we see different packets that follow the same pattern.

01 fc 00 0e 07 12 01 80
01 fc 00 07 07 12 01 80
01 fc 00 09 07 12 01 80
First 8 bytes of controller packets

The 7th byte is set as the same value as the first in every packet. Armed with this, my working structure for the combined first 8 bytes (address + control bytes + 3 bytes of the payload) is,

uint8_t address0;
uint8_t address1;
uint8_t zero;
uint8_t sequence;        /* In range 0-3F */
uint8_t byte0;
uint8_t byte1;
uint8_t copyAddress0;
uint8_t byte2;
Provisional header for a transmitted packet.

Given the way the address byte is used and how the second byte is used by both controller and transmitter I'm inclined to think the address0 and copyAddress0 are actually more akin to command bytes.

AES Bytes?

Using the default key I decrypted the final 16 bytes with differing results.

ff 53 ... => 7f e4 99 45 4b a6 ac 40 ce 79 61 02 5e b2 b0 8a
ff c6 ... => 94 df a7 19 1c a5 71 51 21 ee c8 e3 2e 75 85 b4
ff fc ... => 02 01 0a 6c 72 38 00 00 20 37 5b 00 00 d0 15 83
ff 53 ... => c0 e7 96 46 97 57 68 fc 83 c2 af 74 90 7b 37 4f
ff c6 ... => aa 27 fe 27 0d 0d de b0 6b ab 86 f0 65 39 a1 a4
ff fc ... => 02 01 0a 6c 72 38 00 00 20 5f 5b 00 00 d0 15 81
ff 53 ... => ee 54 ff ef 6e eb c2 d2 dd e5 ec 74 32 d4 cc e8
ff c6 ... => 5f a6 23 c8 13 1f 1c b5 a9 40 da 2f 23 0a 73 23
ff 53 ... => 4c c7 ef 1b c1 9c 4a bf cd e2 25 94 3c dd d7 40
ff c6 ... => 96 a1 e2 8a fd dd c9 5c de 64 fc 37 e6 62 27 dd
ff fc ... => 02 01 0a 6c 72 38 00 00 20 f9 ff ff ff 00 00 f7
ff 53 ... => 19 6d 9b 66 15 ae a1 8f 6e f0 23 cc 96 d0 4c 1c
Decrypted data bytes from heartbeat packets

When the "source address" is 0xfc the default key works and the decrypted bytes look like they are consistent. With 0x53 or 0xc6 the decryption fails which suggests the key needs to be generated. Again, this is supported by the decompiled code and so the next step is to figure out the key generation code.

Checking with packets sent by the controller,

01 fc ... => 01 71 01 09 01 00 2f b7 32 75 a4 43 c3 64 7f f2
01 fc ... => 01 74 01 0b 01 2a 46 ca 42 6c 1a ea fc 99 e3 e4
01 fc ... => 01 6e 01 00 01 08 a2 34 c9 4f 7a dd d2 48 d2 e2
01 fc ... => 01 71 01 03 01 00 04 06 2c 3d f0 30 0d 3a fe 58
01 fc ... => 01 71 01 03 01 00 04 06 2c 3d f0 30 0d 3a fe 58
Decrypted data bytes from controller packets

Key Generation

The next step is to review and follow the key generation to try and generate keys so that all packets can be decrypted.