shuckc/pialarm

Any progress?

Opened this issue · 14 comments

Hi,

I'm also working on decoding the Wintex protocol for my Texecom Premier 832, and was wondering if you ever made more progress on this? My repo is at https://github.com/RoganDawes/WintexProtocol, written in Java.

My eventual goal is to integrate the panel with Home Assistant, having made a Python library and documented the relevant memory addresses. Interestingly, the memory addresses appear to vary per panel (not entirely surprising, given the scale from 16 to 640 zones). My zone status bytes are at 0xdb7, for example, and Wintex reads 16 bytes (for 16 zones).

Hi Rogan,
What I got working here ended up being poorly documented. I was able to decode most of the memory of the Elite24 panel, as well as the serial protocol used to read/write it. At first I used logic scope, then switched to ser2net traces, then I was able to write a software model for the panel and connect Wintex directly to that over TCP/IP.

If you setup a Wintex profile for an Elite24 and set it to connect to localhost 10001, then run up python as follows:

$ python udl-server.py --mem elite24.mem

This will present a factory-reset panel, Wintex will ask if you want to download or upload, if you upload a default configuration you will see the udl-server prints out all the writes as it performs them. The panel state is written to the elite24.mem file which has just been created.

You can then edit a single field in Wintex GUI on any screen, and click "Send" -> "Send Current Page" and udl-server will print out exactly which bytes have a modified data applied to them, e.g.:

udl_server 0: started
Sending login prompt
Sending panel identification
Configuration read addr=00649b sz=10
Configuration read addr=005d04 sz=10
Configuration read addr=001678 sz=1
Configuration read addr=001fca sz=7
Configuration read addr=00167e sz=1
  mem: updated 00649b old=00 new=2f
  mem: updated 00649c old=00 new=fc
  mem: updated 00649d old=00 new=56
  mem: updated 00649e old=00 new=50
  mem: updated 00649f old=00 new=85
  mem: updated 0064a0 old=00 new=90
  mem: updated 0064a1 old=00 new=48

E.g. in this case this is 9 sequential bytes, I think a zone config.

If you then stop udl-server.py and run $ python trace2op.py --mem elite24.mem on the same file, then press control-D to end std-input, it will then decode the memory contents to a json structure representing the panel configuration:

$ python trace2op.py --mem elite24.mem
{
    "zones": [
        {
            "name": "ZOME001abcdefghi",
            "name2": "jklmnopqrstuvwxy",
            "type": 1,
            "chime": 0,
            "area": 1,
            "wiring": 1,
            "attrib1": 0,
            "attrib2": 32
        },
        {
            "name": "zome002abcdefghi",
            "name2": "jkmnopqrstuvwxyz",
            "type": 2,
            "chime": 0,
   ...
   ],
    "users": [
        {
            "name": "Chris",
            "pincode": "000",
            "access_areas": 0,
            "flags0": "00",
            "flags1": "00"
        },
        ...
  "keypads": [
        {
            "keypad_z1_zone": 0,
            "keypad_z2_zone": 0,
            "areas": 0,
            "options": 0,
            "sounds": 0,
            "volume": 0
        },

That is as far as I got. I wanted to be able to then push from my mem file to the panel, and to be able to somehow make the json editable and have it encode back to the memory layout, but never finished it.

I documented some of the I/O operations and overall protocol other than the configuration memory.

You should be able to run up udl-server for a bigger panel, you might need to change the size of the memory block. I imagine a lot of the struct layouts in the resulting memory will be similar even if not identical.

My thinking was that once I could convince Wintex it was talking properly to a fake panel, then I could go ahead and use that model to work on a client.

I have spent a bit more time on this recently (see pull request #2)

My new understanding is that Wintex interrogates the configuration memory of the panel to identify various base addresses for various things. This can be seen (in my case), with an 0x18 byte read at 0x1fe6. Every pair of bytes appears to be an address, with the high byte last. By setting these bytes to incrementing values (0x100 apart), one can determine which addresses reference which functionality in the panel.

mem[0x1fe6] = bytes.fromhex("0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c")

For example, my zone status updates are then polled at 0x20 bytes starting at address 0x0800, while Partition status is polled starting at 0x0700. (The correct address for zone status updates for my Premier 832 panel is 0x04f8, fwiw).

Here is an example of the analysis I was able to perform with the new interactive capability in udl_server.py:

######################################################################################
Zones and outputs
######################################################################################

Addresses from ADDR_ZONESTATUS + byte 0 to byte 0x1f of IO represent the 32 zones

[ADDR_ZONESTATUS+zone]:0 is the Zone Status, ie currently active for 1, or quiescent for 0
[ADDR_ZONESTATUS+zone]:1 is the tamper flag
[ADDR_ZONESTATUS+zone]:2 ?
[ADDR_ZONESTATUS+zone]:3 is the test/fail bit
[ADDR_ZONESTATUS+zone]:4 is the alarmed flag
[ADDR_ZONESTATUS+zone]:5 is bypassed
[ADDR_ZONESTATUS+zone]:6 is "auto bypassed"
[ADDR_ZONESTATUS+zone]:7 ?

To bypass zone 0, wintex sends
Live state write addr=ADDR_ZONESTATUS+zone sz=1 data=0xa0 (1010 0000)
and increments the address for zone 1, etc. Clearly expects the panel to set the bypassed flag in response, because the indicator in the app goes on (but clears on the next poll).

Addresses from ADDR_ZONESTATUS+0x20..0x3f also represent the 32 zones

[ADDR_ZONESTATUS+0x20+zone]:5 is the "faulty" flag
No other changes in the app were observed.

Hi Rogan, very interesting. This might cut down the number of magic constants needed to decode the panel output.

My panel serial port hasn't been hooked up ages since I installed the pannel, I recently ran in the Ethernet cable to the attic through so I will see if I can get connected and verify the same reads.

My idea was to be able to do a couple of things:

  • export panel config to json
  • apply such a dump back in (so that I don't have to use Wintex), and finally
  • to serve up the live status of the panel like how the Texecom cloud stuff presumably does

It seemed like some sort of 3-way model is required to bind particular address ranges to data types, and to insert them at a certain point in the json document, with the third aspect being how to batch the adjacent read/write cycles together like Wintex does. If the mapping is done nicely enough it should be possible to produce a dump of the configuraiton memory that links through to the meaning of the bytes.

What you have added with the interactive cli tool is a great step in the right direction!

I'm honestly not sure of the real value in being able to set up a panel without Wintex being involved. To me, that's pretty much a once off kind of thing, and probably best done by the vendor tool, since it is actually fairly readily available. If it were restricted, I'd be totally on board, of course.

However, ongoing monitoring of the panel, and control of the panel (arm/disarm/bypass/etc) makes sense to build a custom tool, especially since there is nothing available that provides the desired functionality. So that's where I plan to invest my efforts - in the dynamic side of the panel.

So I think that figuring out the base addresses for various functions, whether for the static configuration of users, partitions and zones, or dynamic status/control of the panel, partitions and zones is a great start, documenting various applicable constants for different panels(number of zones, partitions, etc) which dictates the number of bytes to read from the base offset.

I dug out some of the traces I'd recorded before, and hooked up Wintex to the udl-server.py. For my little Prem Elite 24 I don't see any indirection/base offsets being read like your table at 0x1fe6. This might be something for more advanced panels or higher software versions.

I always get this prompt every time when I connect to udl-server.py - even after 'Send Data':
image

The trace to that point looks like:

$ python .\udl-server.py --mem .\elite24c.mem
Serving on ('0.0.0.0', 10001), ('::', 10001, 0, 0)
udl_server 0: started
Sending login prompt
Recieved UDL login [49, 50, 51, 52, 53, 54, 55, 56, 49, 50, 51, 52, 53, 54, 55, 56]. Sending panel identification
Configuration read addr=00649b sz=10 data=0x2f,0xfc,0x56,0x50,0x85,0x90,0x48,0x44,0x76,0x11,0x43,0x39,0xce,0xc4,0x19,0x76
Configuration read addr=005d04 sz=10 data=0x54,0x1,0x7,0x94,0x71,0xb6,0x49,0x17,0xa5,0x1b,0x32,0x5a,0x96,0xf9,0x6e,0x49
Configuration read addr=001678 sz=1 data=0x6
Configuration read addr=001fca sz=7 data=0x1,0x0,0x0,0x0,0xff,0xff,0x2c
Configuration read addr=00167e sz=1 data=0x0
Configuration read addr=005c55 sz=2 data=0x28,0xc0
(eval) >

There is something unusual about these two bytes at 5c55. I think they are supposed to read back differently than they are written to. This seems to be what causes that prompt on every load. Normally once the panel data is written from Wintex, you would not expect to get that prompt.

I also had a bug in trace2op.py it was no longer writing to the saved file from serial data, which I have fixed.

That's very interesting, I guess maybe Elite panels were created after the regular Premier ones, and benefited from some standardisation. Ie there is no need to query the addresses (implying they may vary), if they know they are always the same.

Been working on this a bit more in ESPHome, have almost got it sorted, apart from a weird problem that I need local console access to debug. Since I don't really want to be messing with my actual alarm panel, in the top of the cupboard, etc, I set up a test ESP32, with a USB-UART connected to the UART pins of the ESP32, connecting back to my PC, with socat reading from that USB-UART and relaying to a TCP socket on localhost:10001, which is udl-server.py!
So I can test at my desk, or on the go, without actually impacting anything at all! Ain't tech grand!?

Hi Rogan. I did a few things over the new year
I bought the USBCom / Flashing port kit to upgrade my quite old firmware to the new v5/v6 firmware, so I will see if I get this new self-describing memory area showing the zones/areas/users etc when I can take a UDL trace.
Secondly I fitted a couple of the Texecom Communicator boxes to several sites and integrated them with the Texecom cloud offerings. Quite impressive to be able to configure the panel in their own cloud UI and have it push changes into the panel. I assume that they have developed something similar to our code for speaking UDL. The good news is that a communicator seems to act as a IP communicator on the local network, so it's possible for anyone with the communicators to use the UDL protocol without any additional hardware. I will see if I can add a client mode to the app and get a live read out from my panel.

I'd still like my own json backup of the panel config so will press ahead.

Sounds great! Would love to know what the flashing kit consists of, and if I can get an updated firmware flashed to mine too. Although that also supposes that there IS a newer firmware available!

I used this one https://www.ebay.co.uk/itm/194365312096?var=494481967465 - It's a (genuine) 5V FTDI cable, with the texecom UART pin out, then a MAX programmer chip which handles toggling the 12V for programming. The firmware images and flasher tool is on the Texecom site in the login area.

I wasn't able to get the flashing tool to work with Wine under Ubuntu, seems like the CTS pins to control the MAX chip are not available in the driver, or not working. However running a virtual Win 11 image using lxc/lxd following this guide https://discourse.ubuntu.com/t/how-to-install-a-windows-11-vm-using-lxd/28940 worked fine.

Been playing with this some more. Finally got callbacks working properly, with some kind assistance. Now actions like Bypassing a zone, and confirming it as is required work correctly. The main thing that is still missing is Stay/Part arming a zone. I see that you are expecting 2 bytes as arguments to the 'S' command, being area and type. Can I ask how you found this? My copy of Wintex doesn't seem to have support for Part Arming, which means that I am unable to see what it should look like on the wire.

Funny part of my debug stack, is that since the ESP32 has 3 UARTs, I can use one for logging, one for the Wintex protocol, and one for a StreamServer, relaying the UART to a TCP server. Then two wire links to bridge UART2 and UART3 together crossing RX and TX, and I can skip the USB-UART entirely. Socat to the StreamServer, relaying to the UDL server, and I'm done!