Tasp
Full Member
Posts: 215
|
Post by Tasp on Feb 22, 2020 13:18:27 GMT -5
I have an ongoing project whereby I'm interfacing with an external transmitter. This transmitter communicates with LB successfully using Chris Iversons' LBNet UDP DLL, however my external equipment sends me data and expects a reply.
I've found the solution to this in C++ and Python, neither of which I have a clue about, in anyway shape or form.
From what I have learnt, the message I need to respond back with should be in the following format.
Now to me, that should be self explanatory. The message length is 6 so 0x06, If the Function Code is 0x38, message is blank so possibly that is 0x00 or just omitted all together. the parity is based on the first 2 so 59, making the hex to be sent back, 06 38 59. This of course doesn't work!
I've been researching this and other examples show this should be, '\x06\x0D\x0A' in fact in one example code this is hardcoded and apparently works. However, not for me, now I thought it might be the way I was sending the data, however I am already successfully communicating with it for a handshake, so doubtful its that.
This is the code in C++,
// this class describes a single SIA (data) block
class SiaBlock {
public:
constexpr static const int datablock_max = 63; // Max nr of bytes in a SIA data block
constexpr static const int block_overhead = 3; // Nr of overhead bytes in a SIA block
constexpr static const int block_max = (datablock_max + block_overhead); // Max nr of bytes in a SIA block
constexpr static const unsigned char blockheader_length_mask = 63;// Bitmask to get the SIA block length from the SIA block header.
constexpr static const unsigned char blockheader_flag_ack_request = 0x40;
constexpr static const unsigned char blockheader_flag_reverse_chn = 0x80;
constexpr static const int block_retries = 4; // the number of times to keep trying to send data
// Data Acknoledgements
// - After a block with an acknoledgement request has been send, the TX should wait no more
// then 2.5 seconds. Failure to respond should be handled as a communications error.
constexpr static const int block_ack_timeout_ms = 2000; // (use a little less, specs above are for telephone lines)
enum class FunctionCode : unsigned char {
// System blocks
end_of_data = 0x30,
wait = 0x31,
abort = 0x32,
res_3 = 0x33,
res_4 = 0x34,
res_5 = 0x35,
ack_and_standby = 0x36,
ack_and_disconnect = 0x37,
acknoledge = 0x38,
alt_acknoledge = 0x08,
reject = 0x39,
alt_reject = 0x09,
// Info blocks
control = 0x43,
environmental = 0x45,
new_event = 0x4E,
old_event = 0x4F,
program = 0x50,
// Special blocks
configuration = 0x40,
remote_login = 0x3F,
account_id = 0x23,
origin_id = 0x26,
ascii = 0x41,
extended = 0x58,
listen_in = 0x4C,
vchn_request = 0x56,
vchn_frame = 0x76,
video = 0x49
};
union HeaderByte {
unsigned char data;
struct {
unsigned char block_length :6;
unsigned char acknoledge_request :1;
unsigned char reverse_channel_enable :1;
};
};
union BlockData {
unsigned char data[block_max];
struct {
HeaderByte header;
FunctionCode function_code;
unsigned char message[datablock_max];
unsigned char parity;
};
};
// the block data
BlockData block;
void Erase(){
block.header.data = 0;
block.function_code = (FunctionCode)0;
memset(block.message, 0, datablock_max);
block.parity = 255;
}
// constructor
SiaBlock(){ Erase(); }
// (re)generate the parity field
void GenerateParity(){
block.parity = 0xFF;
block.parity ^= block.header.data;
block.parity ^= (unsigned char)block.function_code;
for(size_t i=0; i<block.header.block_length; i++){
block.parity ^= block.message[i];
}
}
// test if function_code contains a valid function code
bool IsFunctionCode();
// convert a function code into a human readable string
const char* FunctionCodeToString(std::string& str);
};
}
#endif
If anyone could shed some light on the C++ side of things that would be awesome.
|
|
|
Post by Rod on Feb 22, 2020 14:55:32 GMT -5
Well you list four bytes, but it is meant to be a six byte message? Also parity suggests bit mode not byte mode. But again documentation is crucial. Your not going to guess your way through this. The slightest hint of documentation will help.
|
|
Tasp
Full Member
Posts: 215
|
Post by Tasp on Feb 22, 2020 16:23:15 GMT -5
No unfortunately there is zero documentation on this, that I can find. Only snippet of others code in various programming languages that I don't understand. I was hoping someone with a little C++ knowledge would be able to take a look at that code and give me a clue. From the various places I've read stuff it seems a pretty basic generic response, at least for the basic stuff.
|
|
|
Post by Chris Iverson on Feb 22, 2020 20:38:04 GMT -5
Ok, some very useful stuff there.
First, a slight correction to the SIA block info quoted above:
That first field is a header byte, NOT a length byte. The length is contained in the header, but two bits of the byte are used for other reasons.
Here's the header byte definition:
union HeaderByte { unsigned char data;
struct { unsigned char block_length : 6; unsigned char acknoledge_request : 1; unsigned char reverse_channel_enable : 1; }; };
A union is a special kind of structure that lets you access the internal bits of the structure in different ways. You can access the entire byte as .data, or you can access the different sub-bits as .block_length, .acknoledge_request, and .reverse_channel_enable.
So, six bits are assigned to the block length, one bit for "acknowledge request"(not sure if this means ACK, along with the 0x38 function code), and one bit for "reverse_channel_enable", whatever that means.
The problem is, we don't know what order the bits are assigned in, and the order is implementation-defined. Every compiler can order the bits differently, as long as the order is consistent within the compiled program. This creates portability issues, since it could result in the bits being in a different order on the wire. We need to know the exact order of the bits.
They can either be little-endian(the bits are assigned smallest-first), or big-endian(the bits are assigned biggest-first).
Lucky, earlier constants declared makes it clear:
constexpr static const unsigned char blockheader_length_mask = 63;// Bitmask to get the SIA block length from the SIA block header. constexpr static const unsigned char blockheader_flag_ack_request = 0x40; constexpr static const unsigned char blockheader_flag_reverse_chn = 0x80;
The bottom six bits of the header byte are the size, the next bit is the ack request, and the top bit is the reverse channel flag.
This translates to LB as follows:
header = asc(mid$(data$, 1, 1)) messageSize = header AND 63 ackFlag = header AND hexdec("40") revChnFlag = header AND hexdec("80")
The next interesting and useful bit is the BlockData definition:
union BlockData { unsigned char data[block_max];
struct { HeaderByte header; FunctionCode function_code; unsigned char message[datablock_max]; unsigned char parity; };
};
This is important, because it defines a SIA block as ALWAYS being the same size, even if less data is transferred. The excess bytes in the message field are just set to null/0 and ignored.
From the constants:
constexpr static const int datablock_max = 63; // Max nr of bytes in a SIA data block constexpr static const int block_overhead = 3; // Nr of overhead bytes in a SIA block constexpr static const int block_max = (datablock_max + block_overhead); // Max nr of bytes in a SIA block
So, there are ALWAYS 63 bytes in the "message" field. There are three bytes of overhead for each SIA block(one byte for the header, one byte for the function code, and one byte for the trailing parity.)
So, in total, a SIA block is always 66 bytes in size.
Next is the parity calculation.
void GenerateParity() { block.parity = 0xFF; block.parity ^= block.header.data; block.parity ^= (unsigned char)block.function_code;
for (size_t i = 0; i < block.header.block_length; i++) { block.parity ^= block.message[i]; } }
This is important because it shows that, while a SIA block is always the same size, ONLY the active data is used in parity calculation. The rest of the empty bytes are ignored. (Actually, the parity calculation would be the same, anyway, because XOR'ing something with zero just comes out with that same something you put into it.)
Parity calculation in LB(assuming the message part is in a string called msg$):
parity = 255 parity = parity xor header
for x = 1 to messageSize parity = parity xor asc(mid$(msg$, x, 1)) next x
Based on all that, I don't know where 0x06 0x0D 0x0A come from. I also don't know if the ack flag in the header is needed. Unless that's supposed to be the contents of the message? And the header is wrapped around that?
0x06 IS the ACK character in ASCII, and if I'm remembering that PDF you posted in the other topic correctly, they wanted every request to end with a CRLF. 0x0D = CR, 0x0A = LF. I could be misremembering that, though.
If the 0x38 function lets the message be completely empty, and if the ack flag is needed in the header, then I'd expect something like this to work:
ackFlag = hexdec("40") messageSize = 0 msg$ = ""
for x = 1 to 63 msg$ = msg$ + chr$(0) next x
header = ackFlag + messageSize functionCode = hexdec("38")
parity = 255 parity = parity xor header
for x = 1 to messageSize parity = parity xor asc(mid$(msg$, x, 1)) next x
block$ = chr$(header) + chr$(functionCode$) + msg$ + chr$(parity)
|
|
|
Post by Rod on Feb 23, 2020 5:49:31 GMT -5
Can we know what the device is? I can't really search out examples unless I know what I am looking for. Chris is showing us that it is a "standard" message system but it covers a myriad of devices. Unless we can get real examples its going to be long and slow. By your own admission you don't really know what you are looking at, we might fare better googling. You can PM me if you don't want to divulge in open forum.
|
|
|
Post by Rod on Feb 23, 2020 7:42:30 GMT -5
If we take the message the machine generates, hex "050100D8C1" it tells us a couple of things. The message is not always 66 bytes I think it is actually driven by the header and so 5 bytes is the message length. Makes sense not to send 66 bytes each message.
The bit manipulation of the single byte header tells me that the length of the message is five bytes, ACK bit is not set and neither is REV bit.
The problem comes when we try to calculate the parity byte. The message I am now assuming is the header byte hex "05" plus the function byte hex "01" plus the data hex "00D8" and the parity byte hex "C1"
This code gets a different parity byte.
msg$="050100d8c1" header=hexdec(mid$(msg$,1,2)) size=header and 63 ack=header and hexdec("40") rev=header and hexdec("80") print "Size ",size print "ACK ",ack print "REV ",rev parity=255 for x= 1 to len(msg$)-2 step 2 parity=parity xor hexdec(mid$(msg$,x,2)) print x,mid$(msg$,x,2),hexdec(mid$(msg$,x,2)),parity next print "Parity ",dechex$(parity),parity
Using the same principle, to send function hex "38" with no data would be hex "043800C3" That is four bytes, header function, data and parity bytes. Whether the data should be there I don't know. But the header should be hex "04" without ACK or REV set.
msg$="043800C3" header=hexdec(mid$(msg$,1,2)) size=header and 63 ack=header and hexdec("40") rev=header and hexdec("80") print "Size ",size print "ACK ",ack print "REV ",rev parity=255 for x= 1 to len(msg$)-2 step 2 parity=parity xor hexdec(mid$(msg$,x,2)) print x,mid$(msg$,x,2),hexdec(mid$(msg$,x,2)),parity next print "Parity ",dechex$(parity),parity
I am guessing that the ACK bit is set if you want a response to your message, most often not set probably. And this is different to the ACK function hex "38"...…...
|
|
|
Post by Rod on Feb 23, 2020 7:50:44 GMT -5
You stated this further back. This is in fact sending the CR LF pair that Chris mentions."060D0A" is not six bytes, it is three bytes. hex "06" is of cource the infamous ASCII ACK character. I suspect there are two ways to communicate with the machine, asc messages and the byte encrypted messages. I see there is a an ASC function but I am racing ahead.
print hexdec("06") print hexdec("0D") print hexdec("0A") 'decimal 6 'decimal 13 'decimal 10 a$ = chr$(6)+chr$(13)+chr$(10) send a$;
|
|
Tasp
Full Member
Posts: 215
|
Post by Tasp on Feb 23, 2020 9:15:15 GMT -5
Ok, firstly thanks Chris for the breakdown of the code, very helpful and in a way that I can understand! (Kinda!). While, I'm sure Chris' code is correct, it isn't sending the correct ack command back to the external equipment. Rod, very good points. The external equipment is an alarm system, the manufacturer of which is irrelevant as it uses an industry standard protocol, SIA, however the document I orginally link to in a previous thread I think is actually the wrong document, as the protocol from these systems doesn't seem to follow it. however they all produce the same datagram. The initial handshake is The corresponding response is This is send to the equipment as CHR$(), as sending the HEX direct isn't accepted. The next message block is 58 chars The correct response to this is unknown, however I think this is a basic response, but not just ACK CHR$(6). All the responses I've read about are 5 bytes long, where there is no 'message' back just the ACK, header and parity. Some references that I've used; linklinklinklink This one refers to CID and SIA, we can ignore the CID linklink
|
|
Tasp
Full Member
Posts: 215
|
Post by Tasp on Feb 23, 2020 9:34:53 GMT -5
There is further examples. However some of these aren't directly SIA. This one has a Texecom (manf) wrapper, that reconstitutes the SIA into Texecom format. link
|
|
|
Post by Rod on Feb 23, 2020 11:45:26 GMT -5
Well sadly I think we are still miles away. We have not yet understood the message format. While the length value in the first message is correct it isn't in the response message you use or in the second response. Neither are the parity bytes what we expect.
If you send this hex phrase 50 01 00 6d ff as five bytes you get the next message. Does it work again if you send it to acknowledge the second message?
Each document contradicts the other. The only thing shared between any of them is that "060D0A" is and ack message do those three bytes work for you?
|
|
Tasp
Full Member
Posts: 215
|
Post by Tasp on Feb 23, 2020 13:08:39 GMT -5
Sadly no, sending 50 01 00 6d ff as a response to the 2nd incoming message doesn't work. Neither does 06 0d 0a.
As you say, the other info seem contradictory which doesn't help when I have limited expertise to start!
What would be good is if I could work out how to package one of the working SIA receivers from gitub etc and actually see what is being returned back from that software.
|
|
|
Post by Rod on Feb 23, 2020 15:28:16 GMT -5
I think your only option is to monitor a conversation between devices. But given the complexity I would look for better things to spend time on.
the examples you link to are having very different conversation. You nee to analyse the real conversation from your device. Given it is an alarm panel it will be expecting multiple conversations from a variety of devices.
we don’t yet know how it lodsand starts a conversation with a device. Whether ther is one port for all devices or multiple ports.
I suspect one port port and multiple devices but how is it addressing each device.
iphones are rubbish for browsing by the way!
|
|
|
Post by Chris Iverson on Feb 23, 2020 16:40:28 GMT -5
Well, first, I'd like to say that I got my parity code wrong. I missed adding the function code into the parity.
This should be added just after the parity calculation against the header:
parity = parity xor functionCode
Second, I've gone through each of those links that you've provided, and they sometimes seem contradictory to each other(the typescript example can create SIA blocks of varying sizes, which the C++ code example seems to imply shouldn't happen.)
Additionally, the format they describe for a SIA block is all the same, but the "SIA Protocol 2" laid out in one document linked to is entirely different. I suspect that may be the format of the message part of each SIA block, and the header/parity info isn't included in that definition.
The raw data you provide doesn't seem to match up with either of those. I can almost see part of a SIA "account number", but the rest of the data in the "message" doesn't match up with the SIA Protocol 2 definition.
Are you sure it's using SIA?
EDIT: In fact, now I'm sure it's not. I tried running the parity calculation on each one of those examples, and it failed every time. Whatever those are, they aren't full SIA blocks. It's possible they're SIA messages, but I'm not familiar enough with them to know for sure.
|
|
Tasp
Full Member
Posts: 215
|
Post by Tasp on Feb 24, 2020 14:53:28 GMT -5
Sigh Guys thanks so much for taking the time to look into this. As SIA has changed in the last 20 years it could be that some of these documents are refering to different versions, however the changes made are suppose to be backward compatible. I'm only left with finding software that can emulate a receiver so I can then sniff the packets to see what the legitimate response is. This is easier said than done as the software is closely guarded, how the people that have created packages on github etc have done it, I'll never know. If it is an earlier version it could be this 2001 versionHowever this doesn't seem to match either! Although I wonder if its supposed to return some info/identifier to confirm it has received it from the correct external equipment. Although saying that I though Chris may have spotted an external call from elsewhere in the program he disassembled. Yes I'm sure it's SIA as that is what I have the equipment set to transmit. Although this is an alarm, it's not monitoring any devices, SIA is used to transmit alarm messages to a monitoring centre, so I wish to collect that info and display it locally. My last resort is to purchase the RS232 communication box and plug that into the openGalaxy project which is where I got the code posted at the top of this thread. Then sniff the RS232 to see if that responds with anything useful. So while I await Jeff Bezos knock on my door with RS232 cables and connectors I found this link which appears to show how it should respond to a valid SIA signal
|
|
|
Post by Rod on Feb 24, 2020 15:04:52 GMT -5
I have spent an entire day searching. There are multiple protocols not one of the twenty I have read about is anything similar to your messages.
It is a secure, propriety, messaging system. It is not open source. You might find the golden manual but I suspect not.
You could find more accessible electronics to do everything this alarm panel can do and 100 times simpler.
|
|