How I Recreated The USA EAS

How I Recreated The USA EAS
An Emergency Alert was issued on my birthday (I turned 6).

I have always been interested in the Emergency Alert System. This most likely comes from my interest in strict protocols being followed to issue specific data points. Along with the EAS in the US, I am interested in the Alert System used in Israel, which I will talk about some other time. For simplicity, I will discuss how I recreated the data communication process using sound in the EAS.

It all starts when the United States declares a particular emergency. Now, this can be initiated in many ways. One of them can come from the president, which is called a Primary Entry Point. If you receive a message from this point, it will be a bad day. This message is then relayed to the appropriate radio stations for each county. They will handle relaying the message to the civilian. Every message must follow the strict protocol to ensure every machine can interpret and decode said message. The message is a string of characters encoded into binary (1's and 0's). Each tone is then created for each 1 and 0 in the message. I'll get into that later.

Every message must start with a preamble. This preamble is built up and 10101011 repeated 16 times. This is essential for calibration. The machine receiving the message must be aware that a message is about to occur. Then, it is followed with ZCZC- a declaration of the start of the message. Followed by it is the originator code. The originator code describes where the alert is coming from. It could be a PEP (Primary Entry Point) or a more common weather alert (WXR). For this example, I will pick WXR to make it more realistic.

So far, we have ZCZC-WXR- and now we need the event code. This describes precisely what we need to look out for. So, if we have a weather alert, we need a weather event. There are dozens of event codes, but I'll pick something easy–FFW. This means Flash Flood Warning. This makes our message: ZCZC-WXR-FFW-.

The next codes are the exact locations for the alert. These locations are represented using 6 numbers. The first number represents if the entire county is represented in the location or a certain cardinal location. For our example, we will use 0 for this number because it's the entire county. The next 2 numbers after that are the state code, and the following 3 make up the county code. You can find a list of these codes here. For this example, I will be using 5 counties in Missouri. Linn, Macon, Chariton, Randolph, and Carroll County. For every location code you have, you separate them with a dash and then an + at the end. This makes our message now: ZCZC-WXR-FFW-029115-029121-029041-029175-029033+.

The next code is 4 digits long. It represents how long until the alert will expire. The format of the time is hhmm, which means if I want this example flash flood warning to expire in 6 hours and 30 minutes, I make this number 0630, followed by a dash. Our message is now: ZCZC-WXR-FFW-029115-029121-029041-029175-029033+0630-.

Now, we need a code to represent when the alert was actually issued. This code is 7 digits long and contains the day and time represented in Ordinal date. Essentially, this is just the day from 1-366. So, if our flash flood warning was issued on June 6th, it would be 158. This is followed by the time of the alert in the UTC time zone. Assuming this Flash Flood alert was issued at 3:45PM EST, it is 8:45 PM UTC. Then you convert this time to 24-hour format (HHMM). This would be 2045 for our alert, making our final result 1582045-. Adding this to our current message gets us: ZCZC-WXR-FFW-029115-029121-029041-029175-029033+0630-158045-.

The final code that will be appended to our EAS Message is the station from which the alert comes. It is comprised of the station's letter callsign. For our example, the radio station serving our alert would most likely be KZZ34 because that is what serves the general area of the counties I specified.

The NOAA station coverage for Northern Missouri

Now that we have our station's callsign, we can finally complete our EAS Message. Which ends up to be ZCZC-WXR-FFW-029115-029121-029041-029175-029033+0630-158045-KZZ34-. Keep in mind that the message ends with a dash.

What we have here is called a SAME Header. This is the protocol used for all messages in the EAS. It stands for Specific Area Message Encoding. We are not done yet with the EAS Message; we need to transform our message into tones that the EAS Devices can properly decode.

When we encode our sequence into binary and add our preamble to it, we get this:

101010111010101110101011101010111010101110101011101010111010101110101011101010111010101110101011101010111010101110101011101010110101101001000011010110100100001100101101010101110101100001010010001011010100011001000110010101110010110100110010001110010011000100110001001101010010110100110010001110010011000100110010001100010010110100110010001110010011000000110100001100010010110100110010001110010011000100110111001101010010110100110010001110010011000000110011001100110010101100110000001101100011001100110000001011010011000100110101001110000011001000110000001101000011010100101101010010110101101001011010001100110011010000101101

For each 1 you see, a Sine wave tone at 2083.3Hz for 1.92ms must be created. Then, for the 0's, it is a Sine wave tone at 1562.5Hz for the same amount of time. This combination of tones creates the iconic scratchy tone. I completed this task with Python, using this function.

def string_to_eas(string, header=True):
    tones = []
    binary_data = string_to_binary(string)
    if header:
        binary_data = ("10101011" * 16) + binary_data

    for i in range(0, len(binary_data)):
        if binary_data[i] == '0':
            tones.append(generate_sine_tone(1562.5, 1.92, 8000))
        elif binary_data[i] == '1':
            tones.append(generate_sine_tone(2083.3, 1.92, 8000))

    return np.concatenate(tones)

You must repeat these combinations of tones 3 times with a 1 second separation.

header_tones = string_to_eas(eas_message)

    for i in range(3):
        full_message.append(header_tones)
        full_message.append(np.zeros(int(8000)))

the full_message array contains everything that is needed for the entire EAS message. The next step is to append the Attention Signal. This was a tone that was designed at a certain frequency to scare people essentially. It is made up of a combined tone of 853 and 960Hz for 8 seconds. I added it using this:

attention_signal1 = generate_sine_tone(853, 8000, 8000)
attention_signal2 = generate_sine_tone(960, 8000, 8000)

attention_signal = attention_signal1 + attention_signal2
attention_signal = attention_signal / np.max(np.abs(attention_signal))

full_message.append(attention_signal)
full_message.append(np.zeros(int(8000)))

After another second of silence, it is time to initiate the text-to-speech audio. I struggled a lot with finding what matched closest to the typical EAS voice. After some research, it says that the method used for embedded voice does not matter, so I did not sweat it much. I decided to opt for the classic Paul Voice from DECTalk using this framework.

def create_tts(text, loudness_factor=2):  
    os.chdir("C:\\Users\\joey5\\Downloads\\vs6")

    command = f'say -w temp.wav "{text}"'
    subprocess.run(command, shell=True, check=True)

    data, original_samplerate = sf.read("C:\\Users\\joey5\\Downloads\\vs6\\temp.wav")

    data = resample(data, int(len(data) * 8000 / original_samplerate))

    if data.ndim == 2:
        data = np.mean(data, axis=1)  # Average channels

    data = data / np.max(np.abs(data))

    data = data * loudness_factor

    data = np.clip(data, -1, 1)

    return data

tts_message = create_tts(f"[:dv ap 120] [:dv pr 50] [:dv g5 100] [:dv sm 60] [:dv as 85] [:dv qu 55] {message}")

The weird numbers at the bottom are for customizing how the voice sounds like using the DECTalk in-text command syntax. The text-to-speech message I provided for this example was written by ChatGPT

THIS IS A FLASH FLOOD WARNING FOR THE FOLLOWING COUNTIES IN MISSOURI: LINN, MACON, CHARITON, RANDOLPH, AND CARROLL. AT 8:45 PM, DOPPLER RADAR INDICATED HEAVY RAINFALL ACROSS THE WARNED AREA. FLASH FLOODING IS EXPECTED TO BEGIN SHORTLY. HAZARDS INCLUDE: RAPIDLY RISING WATER IN CREEKS, STREAMS, AND URBAN AREAS. DO NOT ATTEMPT TO CROSS FLOODED ROADWAYS OR WALK THROUGH FLOODED AREAS. TURN AROUND, DON’T DROWN. THIS WARNING IS IN EFFECT UNTIL 10:15 PM. STAY TUNED TO NOAA WEATHER RADIO, LOCAL RADIO, OR TELEVISION OUTLETS FOR UPDATES.

To top off your alert, every message must be finished with the letters NNNN encoded in the same binary-tone representation. It must be repeated 3 times with 1 second of silence in between. This is called the tail, and I did it here:

    for i in range(3):
        full_message.append(string_to_eas("NNNN"))
        full_message.append(np.zeros(int(8000)))

It is time to show off the alert we have been working towards on this blog. Here it is [VOLUME WARNING]:

audio-thumbnail
Eas
0:00
/57.302375

I am still a bit annoyed about the text-to-speech, but it gets the job done, and as I said before, it does not matter per the rules of the EAS.

You can find the code here to make your own custom EAS alert here.