After looking at a lot of decompiled code things started to make more sense...
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
Ignoring the AES encoded bytes for the moment, this looks pretty good.
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.
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,
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.
Using the default key I decrypted the final 16 bytes with differing results.
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,
The next step is to review and follow the key generation to try and generate keys so that all packets can be decrypted.