junzis / pyModeS

Python decoder for Mode S and ADS-B signals
GNU General Public License v3.0
529 stars 151 forks source link

Mode-A/C codes #6

Closed EvenAR closed 6 years ago

EvenAR commented 7 years ago

Hello!

My Mode-S Beast can receive both Mode-S and Mode-A/C at the same time. The received data is sent to Planeplotter using the AVR formet. What I don't understand is how Planeplotter knows which squawk code that belongs to which aircraft. From what I can see by the raw output, the Mode-A/C message only contains the SSR code:

// 6-byte timestamp followed by SSR code @01EA3CC23DBD0225; @01EA3CC36A4B3420; // 6-byte timestamp followed by short Mode-S message: @01EA3CC42EF00261942AF05326; @01EA3CC543CA0221; @01EA3CC586996010; // 6-byte timestamp followed by long Mode-S message: @01EA3CC62FB08D4780BF5811D02937CA0CE8656C; @01EA3CC652C35344; @01EA3CC7BCE85357;

How can I extract which Mode-A/C-code an aircraft is transmitting? And shouldn't the A/C include the altitude as well?

Btw, I really love this project - keep up the good work! :+1:

Best regards, Even

EvenAR commented 7 years ago

So I did some more research, and from what I understand now is that altitude and SSR code is sent though the short (7 byte) Mode-S frames. I will reformulate my question to:

Best regards, Even

junzis commented 7 years ago

@EvenAR, thanks! I am glad this tools is useful.

It is indeed interesting to have the capacity to decode Mode-S short message. I will take a look at it.

fbyrkjeland commented 7 years ago

Here is a code that probably will decode short squitter altitudes ,DF0 - DF1 If you can use it please do!

-------------------------------
def bin2gray(binary, bitlength):
    graycode=binary
    for i in range(1 , bitlength ):
           bit=str(int(binary[i-1])^int(binary[i]))
           graycode=str(graycode[:i])+str(bit)
    return graycode
#---------------------------------          

def gray2bin(graycode, bitlength):
    binary=graycode
    for i in range(1 , bitlength ):
           bit=str(int(binary[i-1])^int(graycode[i]))
           binary=str(binary[:i])+str(bit)
    return binary
-----------------------------------------------------------
#short squitter altitude decoding DF0-DF11
def Altitude(msg): #message in binary
   Metric=msg[6]
   if Metric=="0": 
      Q=msg[4]
#      print (msg, Metric, Q)
      if Q=="1": #q=25
         altitude=int(msg,2)
         N = (altitude&0x0F) | ((altitude&0x20)>>1) | ((altitude&0x1F80)>>2)
         alt=(N*25)-1000
      else: #q=100
# altitude above 50175ft is in 100ft increments
         C1=msg[12]
         A1=msg[11]
         C2=msg[10]
         A2=msg[9]
         C4=msg[8]
         A4=msg[7]
         SPI=msg[6]
         B1=msg[5]
         B2=msg[3]
         D2=msg[2]
         B4=msg[1]
         D4=msg[0]
# standard greycode
         graycode=D2+D4+A1+A2+A4+B1+B2+B4
         N5=int(gray2bin(graycode,8),2)
#in 100-ft steps must be converted
         graycode=C1+C2+C4
         N1=int(gray2bin(graycode,3),2)-1
         if N1==6:N1=4
         if N5%2 !=0 : N1=4-N1
         alt=N5*500+N1*100-1200 
      return alt # in feet
#---------------------------------  
fbyrkjeland commented 7 years ago

This code decodes the Mode 3 DF5 ground to air IDENTcode DF5 Mode 3 decoding 13 bit:

def Mode3decode(msg): 
# 13 bit of data (msg)
    C1=msg[0]
    A1=msg[1]
    C2=msg[2]
    A2=msg[3]
    C4=msg[4]
    A4=msg[5]
    SPI=msg[6]
    B1=msg[7]
    D1=msg[8]
    B2=msg[9]
    D2=msg[10]
    B4=msg[11]
    D4=msg[12]
    byte1=int(A4+A2+A1,2)
    byte2=int(B4+B2+B1,2)
    byte3=int(C4+C2+C1,2)
    byte4=int(D4+D2+D1,2)
    return str(byte1)+str(byte2)+str(byte3)+str(byte4)
junzis commented 7 years ago

Thanks @fbyrkjeland

I did not realize it was gray code for 100ft increment...

It looks great. I will add them to the decoder.

fbyrkjeland commented 7 years ago

I also included the Mode 3 decoding in the next message. I have tested both and they work!

Fb

  1. apr. 2017 kl. 11.12 skrev Junzi Sun notifications@github.com:

Thanks @fbyrkjeland https://github.com/fbyrkjeland I did not realize it was gray code for 100ft increment...

It looks great. I will add them to the decoder.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/junzis/pyModeS/issues/6#issuecomment-292115185, or mute the thread https://github.com/notifications/unsubscribe-auth/AROz0fQM-EYN-2J4YnJwxACIb2d6kbj7ks5rtKx6gaJpZM4JmmbD.

fbyrkjeland commented 7 years ago

Do you happen to have the bit pattern on how to decode DF16 and DF18 ?..

fb

  1. apr. 2017 kl. 11.12 skrev Junzi Sun notifications@github.com:

Thanks @fbyrkjeland https://github.com/fbyrkjeland I did not realize it was gray code for 100ft increment...

It looks great. I will add them to the decoder.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/junzis/pyModeS/issues/6#issuecomment-292115185, or mute the thread https://github.com/notifications/unsubscribe-auth/AROz0fQM-EYN-2J4YnJwxACIb2d6kbj7ks5rtKx6gaJpZM4JmmbD.

junzis commented 7 years ago

@fbyrkjeland, I have not looked at DF 16 or 18 yet.. Not sure if there are many of these two types of messages though.

fbyrkjeland commented 7 years ago

Junzi!

Have tested your concept and I have replaced 2 of your functions of altitude:

In the "df20 altitude" I have made a little change, and the «altitude" function I have replaced with my «Altitude" function attached under. The new «Altitude" function is tested on live data and give out correct altitude for the DF0,DF4,DF17,DF20,D21. You can use some or all of it as you please.

Fb

def df20alt(msg): """Computes the altitude from DF20 bit 20-32 Args: msg (String): 28 bytes hexadecimal message string Returns: int: altitude in ft """ alt="None" if df(msg) != 20: raise RuntimeError("Message must be Downlink Format 20.")

Altitude code, bit 20-32

mbin = hex2bin(msg)
mbin=mbin[19:32]
alt=Altitude(mbin)
return alt

def altitude(msg): """Decode aircraft altitude Args: msg (string): 28 bytes hexadecimal message string Returns: int: altitude in feet """ if typecode(msg) < 9 or typecode(msg) > 18: raise RuntimeError("%s: Not a position message" % msg) msgbin = hex2bin(msg) q = msgbin[47] if q: n = bin2int(msgbin[40:47]+msgbin[48:52]) alt = n * 25 - 1000 return alt else: return None

Altitude message Decoding for DF0,DF4,DF17,DF20,D21

def Altitude(msg): #message in binary Metric=msg[6] if Metric=="0": Q=msg[8] if Q=="1": #q=25 altitude=int(msg,2) N = (altitude&0x0F) | ((altitude&0x20)>>1) | ((altitude&0x1F80)>>2) alt=(N*25)-1000 else: #q=100

altitude above 50175ft is in 100ft increments

     C1=msg[12]
     A1=msg[11]
     C2=msg[10]
     A2=msg[9]
     C4=msg[8]
     A4=msg[7]
     # Metric=msg[6]
     B1=msg[5]
     D1=msg[4]
     B2=msg[3]
     D2=msg[2]
     B4=msg[1]
     D4=msg[0]

standard greycode

     graycode=D2+D4+A1+A2+A4+B1+B2+B4
     N5=int(gray2bin(graycode,8),2)

in 100-ft steps must be converted

     graycode=C1+C2+C4
     N1=int(gray2bin(graycode,3),2)-1
     if N1==6:N1=4
     if N5%2 !=0 : N1=4-N1
     alt=(N5*500+N1*100)-1200

else: alt=str(msg[0:6] + msg[7:13]) alt=bin2int(alt) * 3.28084 # convert to ft return alt # in feet #

return alt

  1. apr. 2017 kl. 22.53 skrev Junzi Sun <notifications@github.com mailto:notifications@github.com>:

@fbyrkjeland https://github.com/fbyrkjeland, I have not looked at DF 16 or 18 yet.. Not sure if there are many of these two types of messages though.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/junzis/pyModeS/issues/6#issuecomment-292316175, or mute the thread https://github.com/notifications/unsubscribe-auth/AROz0bF8Az_ropjReANW2IXUWvzED8iPks5rtVC4gaJpZM4JmmbD.

junzis commented 6 years ago

@fbyrkjeland, I realize that in your code for DF20 altitude decoding the sequence of C1 to D4 is reversed compared to the official document:

If the M bit (bit 26) and the Q bit (bit 28) equal 0, the altitude shall be coded according to the pattern for Mode C replies of 3.1.1.7.12.2.3. Starting with bit 20 the sequence shall be C1, A1, C2, A2, C4, A4, ZERO, B1, ZERO, B2, D2, B4, D4.

Was there a mistake in the official document?

fbyrkjeland commented 6 years ago

Have you tested the code against a real aircraft ? I saw the same thing but realized that my implemenation worked! I will recheck a little later today. I have access to a lot of civilian atc radars and mode S so I will test and give you a proper feedback!

Regards Frode Byrkjeland

Den 21. jul. 2017 kl. 16.54 skrev Junzi Sun notifications@github.com:

@fbyrkjeland, I realize that in your code for DF20 altitude decoding the sequence of C1 to D4 is reversed compared to the official document:

If the M bit (bit 26) and the Q bit (bit 28) equal 0, the altitude shall be coded according to the pattern for Mode C replies of 3.1.1.7.12.2.3. Starting with bit 20 the sequence shall be C1, A1, C2, A2, C4, A4, ZERO, B1, ZERO, B2, D2, B4, D4.

Was there a mistake in the official document?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or mute the thread.

fbyrkjeland commented 6 years ago

I had to think over your e-mail , and I agree that it looks a bit strange. I have rewritten the code to be corresponding to the other bit structure , this make more sense, but I have some other mistake here ….. so I will come back to you once I have solved that.

Fb

############################## new Altitude decoder for graycoded altitude #####################

Altitude message Decoding for DF0,DF4,DF11

def Altitude(bindata): #message in binary Metric=bindata[6] if Metric=="0": Q=bindata[8] if Q=="1": #q=25 altitude=int(bindata,2) N = (altitude&0x0F) | ((altitude&0x20)>>1) | ((altitude&0x1F80)>>2) alt=(N*25)-1000 else: #q=100

altitude above 50175ft is in 100ft increments

     C1=bindata[0]
     A1=bindata[1]
     C2=bindata[2]
     A2=bindata[3]
     C4=bindata[4]
     A4=bindata[5]
     # Metric=msg[6]
     B1=bindata[7]
     D1=bindata[8]
     B2=bindata[9]
     D2=bindata[10]
     B4=bindata[11]
     D4=bindata[12]

standard greycode

graycode=D2+D4+A1+A2+A4+B1+B2+B4

     graycode=C2+C1+B4+B2+B1+A4+A2+A1
     N5=int(gray2bin(graycode,8),2)

in 100-ft steps must be converted

graycode=C1+C2+C4

     graycode=D4+D2+D1
     N1=int(gray2bin(graycode,3),2)-1
     if N1==6:N1=4
     if N5%2 !=0 : N1=4-N1
     alt=(N5*500+N1*100)-1200

else: alt=str(bindata[0:6]) + str(bindata[7:13]) alt=bin2int(alt) * 3.28084 # convert to ft return round(alt/1000, 2) # in kilo feet ############################## END new Altitude decoder for graycoded altitude #####################

  1. jul. 2017 kl. 16.54 skrev Junzi Sun notifications@github.com:

@fbyrkjeland https://github.com/fbyrkjeland, I realize that in your code for DF20 altitude decoding the sequence of C1 to D4 is reversed compared to the official document:

If the M bit (bit 26) and the Q bit (bit 28) equal 0, the altitude shall be coded according to the pattern for Mode C replies of 3.1.1.7.12.2.3. Starting with bit 20 the sequence shall be C1, A1, C2, A2, C4, A4, ZERO, B1, ZERO, B2, D2, B4, D4.

Was there a mistake in the official document?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/junzis/pyModeS/issues/6#issuecomment-317023260, or mute the thread https://github.com/notifications/unsubscribe-auth/AROz0QZgSrSQj7BqHKOVhAF0Sic4ya79ks5sQLu9gaJpZM4JmmbD.

junzis commented 6 years ago

@fbyrkjeland. Many thanks, we are also trying to test the code. Looking forward to see your conclusion too!

junzis commented 6 years ago

@fbyrkjeland, from the official doc, the bits are: C1, A1, C2, A2, C4, A4, ZERO, B1, ZERO, B2, D2, B4, D4

I guess, the D1 is not used for altitude code... would that be the problem?

The correct bit-sequence is indeed as in your previous version of code: D2, D4, A1, A2, A4, B1, B2, B4 | C1, C2, C4

junzis commented 6 years ago

@fbyrkjeland @hv92 I think the issue is finally fixed! There seems to be some error in @fbyrkjeland gray2bin() function. There correct code and test are:

Code

def gray2int(graystr):
    """Convert greycode to binary (DF4, 20 altitude coding)"""
    num = bin2int(graystr)
    num ^= (num >> 8)
    num ^= (num >> 4)
    num ^= (num >> 2)
    num ^= (num >> 1)
    return num

def gray2alt(codestr):
    gc500 = codestr[:8]
    n500 = gray2int(gc500)

    # in 100-ft step must be converted first
    gc100 = codestr[8:]
    n100 = gray2int(gc100)

    if n100 in [0, 5, 6]:
        return None

    if n100 == 7:
        n100 = 5

    if n500%2:
        n100 = 6 - n100

    alt = (n500*500 + n100*100) - 1300
    return alt

C1 = mbin[19]
A1 = mbin[20]
C2 = mbin[21]
A2 = mbin[22]
C4 = mbin[23]
A4 = mbin[24]
# _ = mbin[25]
B1 = mbin[26]
# D1 = mbin[27]     # always zero
B2 = mbin[28]
D2 = mbin[29]
B4 = mbin[30]
D4 = mbin[31]

graystr =  D2 + D4 + A1 + A2 + A4 + B1 + B2 + B4 + C1 + C2 + C4
alt = gray2alt(graystr)

Test


def test_graycode_to_altitude():
    assert modes_common.gray2alt('00000000010') == -1000
    assert modes_common.gray2alt('00000001010') == -500
    assert modes_common.gray2alt('00000011011') == -100
    assert modes_common.gray2alt('00000011010') == 0
    assert modes_common.gray2alt('00000011110') == 100
    assert modes_common.gray2alt('00000010011') == 600
    assert modes_common.gray2alt('00000110010') == 1000
    assert modes_common.gray2alt('00001001001') == 5800
    assert modes_common.gray2alt('00011100100') == 10300
    assert modes_common.gray2alt('01100011010') == 32000
    assert modes_common.gray2alt('01110000100') == 46300
    assert modes_common.gray2alt('01010101100') == 50200
    assert modes_common.gray2alt('11011110100') == 73200
    assert modes_common.gray2alt('10000000011') == 126600
    assert modes_common.gray2alt('10000000001') == 126700

@EvenAR, I think this function is finally implemented and working. If no more bugs found, I will close issue one shortly.

fbyrkjeland commented 6 years ago

Oki! I will implement your version tonight and test it!!

Fb

Hilsen Frode Byrkjeland

Den 25. jul. 2017 kl. 12.35 skrev Junzi Sun notifications@github.com:

@fbyrkjeland @hv92 I think the issue is finally fixed! There seems to be some error in @fbyrkjeland gray2bin() function. There correct code and test are:

Code

def gray2int(graystr): """Convert greycode to binary (DF4, 20 altitude coding)""" num = bin2int(graystr) num ^= (num >> 8) num ^= (num >> 4) num ^= (num >> 2) num ^= (num >> 1) return num

def gray2alt(codestr): gc500 = codestr[:8] n500 = gray2int(gc500)

# in 100-ft step must be converted first
gc100 = codestr[8:]
n100 = gray2int(gc100)

if n100 in [0, 5, 6]:
    return None

if n100 == 7:
    n100 = 5

if n500%2:
    n100 = 6 - n100

alt = (n500*500 + n100*100) - 1300
return alt

C1 = mbin[19] A1 = mbin[20] C2 = mbin[21] A2 = mbin[22] C4 = mbin[23] A4 = mbin[24]

_ = mbin[25]

B1 = mbin[26]

D1 = mbin[27] # always zero

B2 = mbin[28] D2 = mbin[29] B4 = mbin[30] D4 = mbin[31]

graystr = D2 + D4 + A1 + A2 + A4 + B1 + B2 + B4 + C1 + C2 + C4 alt = gray2alt(graystr) Test

def test_greycode_to_altitude(): assert modes_common.grey2alt('00000000010') == -1000 assert modes_common.grey2alt('00000001010') == -500 assert modes_common.grey2alt('00000011011') == -100 assert modes_common.grey2alt('00000011010') == 0 assert modes_common.grey2alt('00000011110') == 100 assert modes_common.grey2alt('00000010011') == 600 assert modes_common.grey2alt('00000110010') == 1000 assert modes_common.grey2alt('00001001001') == 5800 assert modes_common.grey2alt('00011100100') == 10300 assert modes_common.grey2alt('01100011010') == 32000 assert modes_common.grey2alt('01110000100') == 46300 assert modes_common.grey2alt('01010101100') == 50200 assert modes_common.grey2alt('11011110100') == 73200 assert modes_common.grey2alt('10000000011') == 126600 assert modes_common.grey2alt('10000000001') == 126700 @EvenAR, I think this function is finally implemented and working. If no more bugs found, I will close issue one shortly.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or mute the thread.

helmet21 commented 1 year ago

Hello! Sorry to post in a closed issue but I am testing this functionality and can't wrap my head around it.

I am receiving raw messages from an RTL-SDR over the network via readsb. I receive this type of data:

0080
7240
1520
0010
...

And am receiving this error:

RuntimeError: Input must be 13 bits binary string
Exception ignored in: 'pyModeS.c_common.altitude'
Traceback (most recent call last):
  File ".../rawreceive.py", line 15, in handle_messages
    alt = pms.common.altitude(msg_binary)
RuntimeError: Input must be 13 bits binary string

I understand that "0080" will translate to a 16 bit string when using the hex2bin() function so I am not getting why the altitude() function requires 13 bit strings and how do I get 13 bit strings from the 4 character octal codes? I am surely missing something. Help appreciated!

helmet21 commented 1 year ago

I tried using the rtlreader module instead since I also want to collect signal power (RSSI) for each message but now I realize that the _check_preamble() function in rtlreader will probably not allow Mode A/C messages to pass since from what I understand they don't have a preamble (which the _check_preamble() functions seems to check for).

It would be great to have an example from someone who has decoded Mode A/C messages with the library somehow.

helmet21 commented 1 year ago

So for anyone that might be interested in decoding Mode A/C. I couldn't get it to work with the code in this repository and after some research I saw that the pulse length of Mode A/C and the pulse length of Mode S are different so the part of the code that frames the pulses has to be adjusted.

Mode S are 0,5 microseconds for the pulses and then there's a 1 us space afterwards, and Mode A/C are 0.45 us for the first pulse itself and 1 us for the space afterwards. Not adjusting for that makes it very difficult to get correct decoding. Anyone interested could have a look at readsb's repository, the file that decodes Mode A/C has very helpful comments.