================================================================================
AlertDisplay v1.1.0 — User Manual
A companion program for SitRepMapper

Designed and developed by Stephen Clay McGehee, KN4AM
Coding done using Claude Code AI
See SitRepNet.com for more information
================================================================================


OVERVIEW
--------
AlertDisplay watches the messages JS8Call receives and puts the ones
that matter on SitRepMapper's screen. You tell it what to look for with
triggers: a word, a phrase or a code, optionally limited to certain
sending stations or to messages sent to your callsign or your group.
When a message matches, AlertDisplay shows it on SitRepMapper's banner
strip, as a red alert or as a plain notification, for as long as the
trigger says.

Triggers are kept in profiles. A group's coordinator can issue a group
profile so every member watches for the same things, and each member
can add a personal profile of their own. Any number of profiles can be
on at once.

AlertDisplay can also watch SitRepMapper's Status Board (needs
SitRepMapper v1.18.0 or later): the overall picture of which stations
are reporting problems. Status Board triggers raise an alert when a
station becomes flagged, gets worse, improves or clears, or when many
stations are flagged at once, and a standing line on the notification
strip sums up the board. See STATUS BOARD.

Key capabilities:
  - Two levels: ALERT (red strip on SitRepMapper that can't be closed
    there) and NOTIFICATION (pale yellow strip that can be closed)
  - A toolbar color block on SitRepMapper showing that AlertDisplay is
    watching (green), that an alert is up (red), or that a setting
    needs attention (yellow)
  - Match by "contains", "whole word or phrase" or "starts with"; any
    case, taken literally, any characters including punctuation codes
  - Filters on the sending station and on who the message was sent to,
    including {MYCALL} (your station) and {GROUP} (the profile's group)
  - Banner text you set per trigger, with placeholders such as {FROM},
    {TEXT} and {AFTER} (the message without the trigger code)
  - Group profiles: read-only for members, with per-trigger on/off
    switches that survive a new release; Import and Export built in
  - Callsign lists: Always Ignore, Ignore all except, and SitRepMapper's
    IGNORE.TXT
  - Handles multi-line messages, relayed messages and partly received
    messages
  - Active Alerts list with Clear and Clear All
  - Test a Message, with an option to show the result on SitRepMapper
    as a 2-minute TEST item
  - Status Board triggers and a standing summary line, from
    SitRepMapper's Status Board (SitRepMapper v1.18.0 or later)
  - Runs quietly in the background with no window; never transmits;
    makes no sound

AlertDisplay never changes SitRepMapper or its data. It only reads
JS8Call's DIRECTED.TXT, SitRepMapper's settings and SitRepMapper's
Status Board file, and writes its own three display files in
SitRepMapper's companions folder.


LAUNCHING THE PROGRAM
----------------------
AlertDisplay is one program with two parts:

  The settings window   Where you manage profiles, triggers and callsign
                        lists, see and clear active alerts, and start or
                        stop the monitor. Open it only when you need it.
  The monitor           Runs in the background with no window. It reads
                        new traffic, matches triggers and keeps
                        SitRepMapper's display up to date.

Closing the settings window does not stop the monitor.

From the terminal:
  python3 /home/<username>/alertdisplay/alertdisplay.py

Command-line options:
  (none)        Open the settings window
  --monitor     Run the monitor (no window). The login autostart uses
                this.
  --clear       Clear every alert and notification showing now
  --stop        Stop the running monitor
  --version     Show the version number

Requirements: Python 3.9 or newer with tkinter. Nothing else needs to be
installed. AlertDisplay runs wherever SitRepMapper runs: Raspberry Pi,
desktop Linux, and Windows 10/11 (use "python" instead of "python3" on
Windows).


GETTING STARTED
---------------
  1. In SitRepMapper, make sure Settings has your station callsign and
     the path to JS8Call's DIRECTED.TXT. AlertDisplay reads both from
     there. (You can set a different DIRECTED.TXT path in AlertDisplay
     if you need to; see SETTINGS TAB.)
  2. Open the settings window: python3 alertdisplay.py
  3. On the Profiles tab, SitRepNet (@SITREP) is already there and
     turned on (see SITREPNET'S PROFILE below). If your group sent you
     its own profile, add it with Import Group Profile... You can also
     make your own with New Personal Profile...
  4. Press Start Monitor at the top of the window. Within a few seconds
     SitRepMapper's toolbar shows a green block reading "Watching".
  5. Try a trigger on the Test a Message tab. Use Show on SitRepMapper
     to see it on the real banner for 2 minutes.
  6. On the Settings tab, tick "Start the monitor automatically when I
     log in" so it is always watching.


WHAT SHOWS ON SITREPMAPPER
--------------------------
AlertDisplay uses two 3-character codes on SitRepMapper's display:

  ALR   Alerts. The red ALERT strip, and the toolbar color block.
  NTF   Notifications. The pale yellow banner strip.

The red ALERT strip
  One item per active alert. SitRepMapper shows alerts first and never
  drops them, and you can't close the strip in SitRepMapper. An alert
  clears when its time runs out, or when you clear it in AlertDisplay
  (Active Alerts tab, or alertdisplay.py --clear). Use ALR for things
  that must not be missed.

The notification strip
  One item per active notification. You can right-click it in
  SitRepMapper and choose Close. It is still (STATIC) or scrolling
  (SCROLL), set on the Settings tab; SitRepMapper scrolls any banner
  that is too long to fit anyway.
  With the Status Board summary line turned on (Status Board tab), the
  first line is always the board summary, e.g.
      23 stations reporting; 4 flagged (2 F!104, 1 F!505, 1 CommStat),
      last 7 days
  or, with the flagged stations added to it (off by default),
      ... last 7 days | Flagged: KJ5MIW F!104 Problems, CommStat Red; ...
  It ends "(stale)" when SitRepMapper hasn't updated the board for
  over 12 minutes. It never times out and is never dropped to make
  room.

The toolbar color block (code ALR)
  GREEN  "Watching"    Running normally
  RED    "ALERT"       An alert is showing
  YELLOW "No calls"    "Ignore all except" is checked but its list is
                       empty, so no station can trigger alerts
  YELLOW "List err"    A callsign list can't be read, so no station can
                       trigger alerts until it's fixed
  YELLOW "No log"      DIRECTED.TXT isn't set, or can't be read
  YELLOW "No profile"  No triggers are turned on
  GRAY   "Stopped"     The monitor was stopped
  GRAY   (dot)         The monitor stopped updating: it crashed, or the
                       computer was shut down without stopping it
  RED outranks YELLOW: while an alert is up, a settings problem shows
  only in the settings window and the log.

Details
  - A relayed message shows "(via CALL)" after its text, naming the
    station that passed it on.
  - A message that wasn't completely received still triggers. The
    Active Alerts tab marks it "partial".
  - If the same banner text is already showing, a repeat doesn't add a
    second item; it extends the time of the one already there.
  - SitRepMapper allows 500 characters per banner file. When the items
    would pass that, AlertDisplay drops the oldest notifications (never
    the Status Board summary line), but never drops an alert: it
    shortens the longest alerts instead, ending them with "...". The
    full text stays in the Active Alerts tab.
  - No sound of any kind. On many Raspberry Pi setups, using the sound
    system can interfere with the radio interface.
  - AlertDisplay never transmits. If a station needs an acknowledgement,
    use JS8Call's MSG function.


THE MONITOR
-----------
The monitor is the part of AlertDisplay that does the actual watching.
It's a background program with no window of its own: you won't see it
on the screen or the taskbar. While it runs, it reads each new message
JS8Call writes to DIRECTED.TXT, checks it against the triggers in your
profiles and callsign lists, and keeps SitRepMapper's ALERT strip,
notification strip and toolbar block up to date. It also clears each
item when its time runs out.

The monitor and the settings window are separate. The settings window
is only for changing things and looking at what's showing. Opening or
closing it doesn't start or stop the monitor. If the monitor isn't
running, nothing is being watched, even with the settings window open.
The toolbar block on SitRepMapper tells you which: green, red or yellow
means the monitor is running; gray means it isn't.

The line across the top of the settings window shows whether the monitor
is running, since when (UTC), and the first warning if there is one.

  Start Monitor   Starts it in the background. The block turns green
                  within a few seconds.
  Stop Monitor    Stops it. Banners are taken down (nothing would time
                  them out), the list of active items is kept for the
                  next start, and the block turns GRAY "Stopped".

Only one monitor runs at a time; starting a second one does nothing.

How it behaves:
  - It checks DIRECTED.TXT every 3 seconds, and only reads the lines
    added since the last check.
  - On start, it begins at the end of DIRECTED.TXT, so old traffic
    doesn't set off alerts. Alerts that were showing when it stopped
    come back if their time hasn't run out.
  - Changes you save in the settings window (profiles, lists, settings)
    reach the running monitor within a few seconds. No restart needed.
  - It rewrites SitRepMapper's display files every minute. If the
    monitor crashes, the banners clear within 5 minutes and the block
    turns gray within 3, so a red strip can't be left up for long.
  - If DIRECTED.TXT is replaced or cut short, it reads the new file from
    the start but skips messages more than 30 minutes old.
  - It also checks SitRepMapper's Status Board file every 3 seconds,
    and reads it only when SitRepMapper has changed it (see STATUS
    BOARD).

Starting automatically
  Settings tab > "Start the monitor automatically when I log in". This
  creates ~/.config/autostart/alertdisplay.desktop on Linux and the Pi,
  or AlertDisplay.cmd in the Startup folder on Windows. Untick it to
  remove it.


ACTIVE ALERTS TAB
-----------------
Lists every alert and notification showing on SitRepMapper now, alerts
first. Times are UTC.

  Type         ALERT or Notification, plus "(partial)" for a message
               that wasn't completely received, or "(test)" for an item
               from Show on SitRepMapper
  Received     When AlertDisplay received it
  From         The station that sent it (for a relay, the station that
               sent it first)
  Banner text  What SitRepMapper shows
  Clears in    Time left before it clears by itself

Select an item to see the whole message, who it was sent to, and which
profile and trigger it matched.

  Clear       Clears the selected items (select several with Ctrl or
              Shift)
  Clear All   Clears everything, alerts and notifications

From the terminal, python3 alertdisplay.py --clear does the same as
Clear All. It works for a desktop shortcut, too.


PROFILES
--------
A profile is a file of triggers. There are two kinds, both in the same
format (AlertDisplay_ProfileBuilding.txt, in the program folder, is a
step-by-step guide to building them, with worked examples):

  Group      Issued by a group's coordinator and imported by members.
             Opens read-only. Replaced as a whole when a new release is
             imported.
  Personal   Your own. Never shared unless you export it.

SitRepNet's profile
  SitRepNet (@SITREP) comes with AlertDisplay
  (AlertDisplay_SitRep_Group_Profile.json in the program folder). The
  first time AlertDisplay starts, it installs it and turns it on, so
  every station has the same base of alerts. A newer AlertDisplay that
  ships a newer release of it replaces the installed one by itself;
  your own on/off choices for its triggers carry over. If you turn the
  profile off, it stays off. If you remove its file from the profiles
  folder, it isn't put back. The log notes each install or update.
  Don't change it with Edit Anyway...: the next release replaces it,
  changes and all. Turn single triggers off with Turn On/Off, or use
  Duplicate as Personal for your own version.

  Your group's own profile goes alongside it. Coordinators start from
  AlertDisplay_Group_Template.json in the program folder: it has only
  the triggers tied to a group callsign. Import it (it comes in as a
  personal profile, so you can edit it), set its Name and Group
  callsign, edit it, then use Export as Group Profile... The exported
  file is the group profile your members import; your own copy stays
  personal for the next release. See "Adding your group" in
  AlertDisplay_Overview.txt.

Profiles tab
  On           Whether the profile is in use
  Name
  Kind         group or personal
  Release      The group's release number (e.g. 2026-11-07.1), so
               members can check they have the latest
  Triggers on  How many of its triggers are on
  Status       OK, a count of problems, or "Not usable"

  Select a profile to see its file, its description and any problems in
  the box below the list. Double-click to open it.

  Profile order matters. When one message matches triggers in several
  profiles, the profile higher in the list gives the banner text. An
  alert (ALR) always wins over a notification (NTF), whatever the
  order. The message shows only once either way; the other matches are
  noted in the log.

Buttons
  Turn On/Off               Use or stop using the selected profile
  Move Up / Move Down       Change its place in the order
  Open...                   Open it in the profile editor
  Remove...                 Delete its file (asks first)
  New Personal Profile...   Asks for a name, makes an empty personal
                            profile, turns it on and opens it
  Import Group Profile...   Choose a profile file. AlertDisplay checks it
                            first and refuses a file with errors, listing
                            them. If you already have that profile (same
                            profile_id), it asks before replacing it with
                            the new release. Triggers you turned on or off
                            yourself stay that way. A newly imported
                            profile is turned on.
  Duplicate as Personal     Copies the selected profile as a personal one
                            named "... (my copy)" that you can edit. The
                            copy starts turned off, so the same alerts
                            don't come from two profiles; turn it on and
                            the original off when it's ready.
  Export as Group Profile...
                            Writes a copy for group members to import.
                            Asks for the Author, the Release (offers
                            today's date plus .1, .2 ...), the group
                            callsign and the Profile ID, marks the copy
                            as a group profile, checks it, and refuses
                            to export it if anything is wrong. Your own
                            copy isn't changed.
                            Profile ID: how a later release finds the
                            one it replaces, so it must stay the same
                            for every release and differ from every
                            other group's. A group profile keeps its
                            own. A "my copy" (from Duplicate as
                            Personal) is offered one made from the
                            group callsign (@MYGROUP -> mygroup), and
                            "(my copy)" is taken off the name members
                            see. It warns if a profile for a different
                            group on this station already uses the ID.

A profile file placed in ~/.config/alertdisplay/profiles/ by hand shows
up in the list turned off.


THE PROFILE EDITOR
------------------
Opened with Open... or a double-click on the Profiles tab.

Top of the window
  Name, Group callsign, Description, and a line showing the kind, ID,
  release and author.
  Defaults: Display, Minutes, Cooldown and Banner text for any trigger
  that leaves them blank. Leave these blank to use the built-in values
  (see TRIGGERS).

Trigger list
  Shows each trigger: On, Name, Match, Display, and From / To. Triggers
  are checked in this order, and the first match gives the banner text,
  so put specific triggers (e.g. "net activation") above general ones
  (e.g. "ALERT").

  Add...                 New message trigger
  Add Status Board...    New Status Board trigger (see STATUS BOARD)
  Delete                 Delete the selected trigger
  Move Up / Move Down    Change its place
  Edit... (View...)      Open the selected trigger (View... for a
                         read-only group profile)
  Turn On/Off            Turn the selected trigger on or off

Bottom of the window
  Save, Close. Closing with unsaved changes asks whether to save.

Group profiles
  A group profile opens read-only: the coordinator issues it, and
  members shouldn't edit their copy. You can still turn single triggers
  on or off with Turn On/Off. That choice is kept in your own settings,
  not in the profile, so it survives a new release of the group
  profile.

  Duplicate as Personal   Make your own editable copy
  Edit Anyway...          For the group's coordinator: unlocks editing.
                          After saving, use Export as Group Profile on
                          the Profiles tab to give members the new
                          release.


TRIGGERS
--------
A trigger says what to look for and what to show. There are two kinds:
message triggers, which look at DIRECTED.TXT and are described here,
and Status Board triggers (see STATUS BOARD). The message trigger
window (Add... or Edit... in the profile editor) has these fields:

  Name               Shown in the settings window and the log
  Description        Optional notes
  Match              Contains, Whole word or phrase, or Starts with
  Match text         One per line. The trigger fires if any of them is
                     found.
  From               Sending callsigns, one per line. Blank = any
                     station.
  To                 Who the message was sent to: callsigns, @GROUPs,
                     {MYCALL}, {GROUP}, one per line. Blank = anyone.
  Display            Profile default, ALR (Alert, red strip) or NTF
                     (Notification)
  Minutes shown      How long the item stays up unless cleared sooner
  Cooldown minutes   The same trigger, sending station and text inside
                     this time is ignored. JS8 messages are often
                     repeated and relayed.
  Banner text        What shows on SitRepMapper (placeholders below)
  On                 Whether the trigger is in use

Blank Display, Minutes, Cooldown and Banner text use the profile's
defaults, or these built-in values:
  Display NTF; 60 minutes for ALR, 30 for NTF; cooldown 10 minutes;
  banner text "{FROM}: {TEXT}"

Match types
  Matching ignores case, and the text is always taken literally: no
  character has a special meaning. Matching looks at the message text
  after the TO callsign.

  Contains               The text appears anywhere.
  Whole word or phrase   The text appears with no letter or digit
                         touching it on either side. MAGNET matches
                         "@MAGNET SNR?" but not MAGNETIC or
                         WWW.MAGNETHF.COM. A hyphen counts as a
                         break: SITREP-ALERT matches "SITREP-ALERT
                         STORM" but not SITREP-ALERTS.
  Starts with            The message begins with the text, right after
                         the TO callsign. A quoted or forwarded copy
                         further into a message doesn't fire it, but
                         neither does a message sent with MSG
                         ("@SITREP MSG ...").

  Any string can be a trigger: a word (ALERT), a phrase (TORNADO
  WARNING), or a code. A group's own alert code takes the standard
  form (group)-ALERT, e.g. SITREP-ALERT for @SITREP or LCARES-ALERT
  for @LCARES, matched as a whole word. It uses only letters and a
  hyphen, so it arrives intact. Send it over the air once anyway
  before your group relies on it.

  A word note from real traffic: ALERT is the term groups actually use;
  EMERGENCY turns up now and then; MAYDAY and SOS almost never. Plain
  words also appear in everyday chatter and in automated weather posts,
  so a code such as SITREP-ALERT, or a From or To filter, gives far
  fewer false alarms than a word on its own.

From and To
  - A callsign also covers the same station with a suffix or prefix:
    KN4AM covers KN4AM/P. List KN4AM/P to cover only that one.
  - From is the station that sent the message first. For a relayed
    message, that's the station after *DE*, not the relay.
  - {MYCALL} is your station callsign from SitRepMapper's settings. A
    trigger that uses it is skipped (with a warning) until a station
    callsign is set.
  - {GROUP} is the profile's Group callsign, so one group file works for
    every member without repeating it.
  - The callsign lists (see CALLSIGN LISTS) are applied first.

Banner text placeholders
  {FROM}     The station that sent the message first
  {TO}       Who it was sent to
  {TEXT}     The message text
  {AFTER}    The message text after the matched trigger text, so the
             trigger code doesn't show. Anything before the match is
             left out too. If nothing follows the code, the trigger's
             name is shown instead.
  {TIME}     When it was received, UTC (e.g. 16:05Z)
  {TRIGGER}  The trigger's name
  {GRID}     The grid reported in the message as GR[...]. If the
             message has no GR[...], it shows ?.
  {DIST}     About how far that grid is from your Station grid (set
             in SitRepMapper, Settings > General), centre to centre,
             in miles, e.g. ~69 mi.
  {DIR}      Which way it is from your Station grid: N, NE, E, SE, S,
             SW, W or NW.
  {RANGE}    Both together, e.g. ~69 mi N.
             For all three: "~" means one of the grids is only 4
             characters (a square about 70 by 50 miles), so the
             distance could be off by up to about 40 miles. With two
             6-character grids it's within a few miles and the ~ is
             left off. "Local" means the reported grid is your own
             grid, or one grid lies inside the other (e.g. EM15 and
             EM15FP). ? means the message has no grid, or your
             Station grid isn't set in SitRepMapper.

  Example: Match "Contains" F!104, Display NTF, Banner text
  F!104 from {GRID}. The message
      W2KI: @SITREP  F!104 100 ST[NC] GR[FM05OW] #GXIS
  shows on the banner as
      F!104 from FM05OW
  With Banner text F!104 from {GRID} ({RANGE}) and a Station grid of
  EM15FP, it shows
      F!104 from FM05OW (1049 mi E)

  Example: Match "Whole word" SITREP-ALERT, Display ALR, Banner text
  {AFTER}. The message
      @SITREP SITREP-ALERT STORM SURGE WARNING FOR DAYTONA BEACH
  shows on the red strip as
      STORM SURGE WARNING FOR DAYTONA BEACH

When one message matches several triggers
  It shows once. An alert wins over a notification; among the same
  level, the first matching trigger (in profile order, then trigger
  order) gives the banner text. The other matches are noted in the log.


CALLSIGN LISTS
--------------
The Callsign Lists tab sets which stations may trigger alerts.

  [ ] Always Ignore         Never act on these stations   [Edit List...]
  [ ] Ignore all except     Act only on these stations    [Edit List...]
                            (* means everyone)
  [ ] Also ignore stations in SitRepMapper's IGNORE.TXT

  - With neither of the first two boxes checked, every station can
    trigger alerts.
  - An empty "Ignore all except" list means NOBODY can trigger alerts.
    The block turns YELLOW "No calls" as a reminder.
  - Always Ignore wins if a callsign is on both lists.
  - Fail safe: a checked list that can't be read means no station can
    trigger alerts until it's fixed (YELLOW "List err").
  - A callsign also covers the same station with a suffix: KN4AM covers
    KN4AM/P.
  - IGNORE.TXT lives in SitRepMapper's program folder. AlertDisplay looks
    for that folder in the usual places; set it on the Settings tab if
    it isn't found. No IGNORE.TXT file means nobody is ignored through
    it.
  - JS8 callsigns aren't verified. The lists keep out noise and
    mistakes, but they can't prove who sent something.

Edit List... opens the list in a small editor. The lists are plain text
files, one callsign per line, any case, with # starting a comment (the
same format as IGNORE.TXT), so any text editor works too:
  ~/.config/alertdisplay/always_ignore.txt
  ~/.config/alertdisplay/ignore_all_except.txt

The Status box under the checkboxes shows warnings (Warning), things
worth knowing (Note) and the overall state (Info).

A message from a station the lists keep out is not shown, but the log
records that it matched, so you can tell why nothing appeared.

Status Board triggers: Always Ignore and IGNORE.TXT apply to them too,
so an ignored station never raises a Status Board alert. "Ignore all
except" does NOT apply to them; it is only for DIRECTED.TXT messages.


TEST A MESSAGE
--------------
Type a message the way it would arrive, for example
    KN4AM: @SITREP ALERT NET ACTIVATED
or paste a whole line (or several) from DIRECTED.TXT. Lower case is
fine; JS8 sends upper case only, so the test does too. The test uses
the saved profiles and callsign lists.

  Test                   Shows the result in the window only: who sent
                         it, who it was sent to, the text, every trigger
                         that matched, which one is used, and the exact
                         banner text, or why it wouldn't show.
  Show on SitRepMapper   Does the same, then puts the result on
                         SitRepMapper as a real item for 2 minutes, with
                         its banner text starting "TEST:". A test alert
                         turns the block RED, just like a real one.
                         Clear it early on the Active Alerts tab. The
                         monitor must be running. Nothing is shown if no
                         trigger matched or the callsign lists would
                         keep the sender out.

Neither button starts a cooldown, so the same message arriving over the
air afterwards still shows normally.

Status Board triggers aren't tested here. The Status Board tab shows
what they're watching.


STATUS BOARD
------------
Needs SitRepMapper v1.18.0 or later.

SitRepMapper's Reports > Status Board shows each station's latest
report of each type (F!104, F!309, F!505, F!500 and CommStat), and
flags the ones that cross Settings > Sub-Optimal Thresholds.
SitRepMapper also saves that picture to a file for companion programs,
updated within seconds of any change. AlertDisplay reads it, so what
AlertDisplay says and what SitRepMapper's screen shows always agree.

"Flagged" always means flagged by SitRepMapper's Sub-Optimal
Thresholds. To change what counts as flagged, change the thresholds in
SitRepMapper.

Status Board triggers
  Added to a profile with Add Status Board... in the profile editor.
  They live in profiles like message triggers, so a coordinator can
  issue them in a group profile and members can turn them on or off.

  Event                   Fires when
  Station newly flagged   A station's report of a watched type becomes
                          flagged (including a station's first report).
  Station got worse       A flagged report gets worse (F!104 Degraded to
                          Problems, CommStat Yellow to Red, F!309
                          severity up, F!505 score down, F!500 worse
                          answer), or a station already flagged on one
                          type becomes flagged on another. An F!309 at
                          the same severity counts as worse when its
                          Current Status changes to "Ongoing,
                          Deteriorating".
  Station improved        The mirror of got worse: a flagged report is
                          still flagged but less bad than before
                          (e.g. F!309 Severe to Moderate, or the same
                          severity now "Ongoing, Improving"). When it's
                          no longer flagged at all, that's Station
                          cleared instead.
  Station cleared         A station sends a NEWER report that isn't
                          flagged, or its F!309 is no longer flagged
                          because it was reported Resolved or its own
                          Expiration passed. A report that just ages
                          out of the Status Board's window doesn't
                          count: nothing improved, so that is only
                          logged.
  Flagged count reaches N N or more stations flagged.
  Flagged share reaches   That percent of the stations reporting are
  X%                      flagged, with at least the minimum number of
                          stations reporting (default 5), so "1 of 2 =
                          50%" doesn't set it off.

  Count and share fire once when they cross the line, and again only
  after dropping back below and rising past it again. An ALR from one
  is removed when it drops back below. Tick "Also notify (NTF) when it
  drops back below" to show a notification then too, with its own
  text (built in: {TRIGGER}: back below, {COUNT} of {STATIONS}
  flagged).

  Other fields
    Report types          Tick the types to watch; none ticked = all.
    Narrow it down        Optional: F!104 Problems only, CommStat Red
                          only, F!309 minimum severity or categories,
                          F!505 score below a number, F!500 worst answer
                          is one of a list (e.g. water gasoline). These
                          only narrow SitRepMapper's flags. They can't
                          flag anything SitRepMapper doesn't.
    Display, Minutes shown, Cooldown minutes, Banner text, On
                          As for message triggers. The cooldown is per
                          station and report type, so a station flipping
                          between flagged and not doesn't keep alerting.

  Banner text placeholders
    {FROM}      The station's callsign (its grid if it has none)
    {GRID}      The report's grid. For an F!309 that's where the
                incident is, which may not be where the station is.
    {DIST} {DIR} {RANGE}
                How far and which way that grid is from your Station
                grid, as for message triggers (e.g. ~69 mi N)
    {TIME}      The report's time, UTC
    {TRIGGER}   The trigger's name
    {TYPE}      The report type, e.g. F!104
    {STATUS}    The new status, e.g. Problems
    {REASON}    SitRepMapper's reason for the flag
    {WAS}       The status before
    {COUNT}     Stations flagged
    {STATIONS}  Stations reporting
    {DAYS}      Days the Status Board covers
  The profile's default banner text is for message triggers only. A
  Status Board trigger with no banner text of its own uses a built-in
  one, e.g. "{FROM} flagged {TYPE}: {STATUS} ({GRID})".

How Status Board alerts behave
  - When the monitor starts, the board as it is then is the starting
    point: nothing fires for stations already flagged. If stations
    are flagged, Status Board triggers are on and the summary line is
    off, one notification sums up the board instead.
  - An ALR clears by itself when the station sends a newer report that
    isn't flagged (or that no longer meets the trigger's narrowing), or,
    for a count or share trigger, when the count drops back below. A
    "got worse" ALR also clears when the station's newer report of that
    type is still flagged but better, since it describes the worse
    state (the station's first alert stays).
  - If you change SitRepMapper's Sub-Optimal Thresholds or "Days to
    include", the flags all shift at once. That is logged and taken as
    the new starting point; it doesn't raise alerts.
  - One event, shown once: a message trigger (e.g. one matching F!104)
    and a Status Board trigger for the same station and report type
    within about 2 minutes are the same event. Whichever arrived first
    stays; if the second is an ALR and the first an NTF, it becomes an
    ALR and shows the ALR's text, trigger and profile (the Active
    Alerts tab names the ALR's trigger). If that ALR text is already
    showing, it isn't shown twice. When both are NTFs, the Status
    Board's text and trigger replace the message's, because the board's
    says what the report is (e.g. "Outage: N0ABC Severe: Power Failure"
    rather than "Incident report from N0ABC"). The longer of the two
    "Minutes shown" is used.
  - When several Status Board triggers fire for the same station and
    report, it shows once: ALR over NTF, then the first in profile and
    trigger order.

Status Board tab
  - Whether the Status Board file was found, when SitRepMapper last
    updated it, and the dates it covers. "STALE" means SitRepMapper
    hasn't updated it for over 12 minutes (it isn't running). "Not
    found" means SitRepMapper is older than v1.18.0 or hasn't run yet.
  - The summary, and every flagged station and report type.
  - The standing line settings:
      Show a Status Board summary line      On by default
      Add the flagged stations to it, up to N stations, then "+N more"
                                            Off by default
    "Shows as:" previews the line. Save to apply; a running monitor
    picks it up within a few seconds.


SETTINGS TAB
------------
  DIRECTED.TXT           Leave blank to use the path set in
                         SitRepMapper. The line below shows the path in
                         use and whether it was found.
  SitRepMapper folder    SitRepMapper's program folder. Only needed for
                         IGNORE.TXT. Leave blank to look in the usual
                         places.
  Notifications          Still (STATIC) or Scrolling (SCROLL). Alerts
                         always use the red ALERT strip.
  Save                   Saves these three. A running monitor picks
                         them up within a few seconds.

  Start the monitor automatically when I log in
                         Takes effect immediately (no Save needed).
  Check for updates when this window opens
                         On by default; takes effect immediately. See
                         UPDATE CHECK below.
  Check for Updates Now  Checks right away, whatever the box says.

The bottom of the tab shows the version and where AlertDisplay keeps
its files.

UPDATE CHECK
  When the settings window opens (if "Check for updates when this
  window opens" is ticked), AlertDisplay fetches a small
  alertdisplay_version.json file from SitRepNet.com in the background
  and compares the version it reports with the one running. If a
  newer version is available, a box offers to open the download page;
  if not, or if the check fails (no internet, server unreachable),
  nothing is shown. The monitor never checks, and AlertDisplay works
  the same with no internet connection.

  Check for Updates Now always reports a result: "up to date," the
  update-available box, or a "check failed" warning. No update is ever
  downloaded or installed for you; you install it from the download
  page the same way as before.


LOG TAB
-------
Shows the newest entries of AlertDisplay's log, oldest at the top.
Refresh reloads it. The log records:
  - Monitor start and stop, and the DIRECTED.TXT being watched
  - Settings loads, with the number of triggers on
  - Profile problems and other warnings
  - Every message that matched a trigger: shown (ALR or NTF), ignored
    because of a callsign list, or a repeat inside the cooldown
  - Every Status Board event, including ones not shown: a flagged
    report that aged out, a threshold change, stale data, and alerts
    cleared automatically
  - Clears, expiries and tests

The log is ~/.config/alertdisplay/alertdisplay.log. Past about 200 KB it
is cut back to its newest 100 KB.


HOW DIRECTED.TXT IS READ
------------------------
JS8Call writes one line per message received, for example (the gaps are
tabs; the time is UTC):

  2026-05-24 01:50:26  14.078000  1500  -03  N0BBM: KI5MUY INFO? ♢

  - The sending station is the callsign before the colon. The first word
    after the colon is who it was sent to (TO). The rest is the text.
  - "♢" ends a complete message. A message without it was only partly
    received; JS8Call also writes "……" where parts were missed. Both
    still trigger, marked partial.
  - A long message carries on over extra lines with no time at the
    start. Those lines belong to the message above and are checked
    together with it. They are never read as a new message, even when
    they look like one (a quoted "K1ABC: ..." line, for instance).
  - Relays look like this:
        WO7I: WD9DUI> ACK *DE* WD4KAV ♢
    WO7I passed on to WD9DUI a message that WD4KAV sent first. {FROM},
    the From filter and the callsign lists all use WD4KAV, and the
    banner adds "(via WO7I)".
  - A line whose sending callsign has a space in it is a bad decode
    and is skipped.
  - A message with no "♢" is treated as finished when the next message
    arrives, or after 15 seconds.

AlertDisplay only ever reads DIRECTED.TXT, and never keeps it open.


PROFILE FILE FORMAT
-------------------
Profiles are JSON text files. You don't need to edit them by hand (the
settings window does it all), but the format is plain so a coordinator
can. JSON has no comments, so profiles and triggers have a
"description" field for people reading the file.
AlertDisplay_SitRep_Group_Profile.json, in the program folder, is a
complete profile in use (33 triggers, message and Status Board).

Profile fields
  format           Always "alertdisplay_profile". Lets AlertDisplay
                   reject a file that isn't a profile.
  version          Format version, "1.1" (1.1 added Status Board
                   triggers; "1.0" profiles still work). Version 1
                   formats are only ever added to, never changed.
                   AlertDisplay refuses 2.x.
  kind             "group" or "personal". Group opens read-only.
  profile_id       Stable identifier. Importing replaces the profile
                   with the same profile_id, and your own trigger on/off
                   choices are filed under it. Lower case letters,
                   digits and hyphens.
  name             Shown in the settings window.
  profile_version  The group's release, e.g. "2026-11-07.1". Stamped by
                   Export as Group Profile.
  author, date     Stamped on export.
  description      Free text.
  group            Optional. The group callsign used by {GROUP}, e.g.
                   "@SITREP". Set once here so the triggers don't
                   repeat it.
  defaults         Optional. Values for any trigger that leaves them
                   out: display, minutes, cooldown_minutes,
                   banner_text.
  triggers         The list of triggers, in the order they're checked.

  A personal profile is the same with "kind": "personal". It usually
  leaves out profile_version and group.

Trigger fields
  id                Required, stable, unique within the profile. Your
                    on/off choices for group triggers refer to it, so a
                    coordinator must never reuse or renumber one.
  name              Required.
  description       Optional.
  enabled           Optional, default true. In a group profile this is
                    the coordinator's default; each member can still
                    turn the trigger on or off.
  match             Required:
                      "type"  "contains", "word" (whole word or phrase)
                              or "starts_with"
                      "text"  a list; the trigger fires if any item
                              matches
  from              Optional. Sending callsigns allowed.
  to                Optional. Callsigns, @GROUPs, "{MYCALL}",
                    "{GROUP}".
  display           "ALR" or "NTF".
  minutes           Whole minutes, 1 or more.
  cooldown_minutes  Whole minutes, 0 or more.
  banner_text       Text with placeholders {FROM} {TO} {TEXT} {AFTER}
                    {TIME} {TRIGGER} {GRID} {DIST} {DIR} {RANGE}.

Status Board trigger fields
  These have "source": "statusboard" and no match, from or to. id,
  name, description, enabled, display, minutes, cooldown_minutes and
  banner_text are as above (banner_text uses the Status Board
  placeholders, and the profile's default banner_text doesn't apply).
  source            "statusboard". Left out means a message trigger.
  event             "newly_flagged", "worse", "improved", "cleared",
                    "count" or "share".
  types             Optional. A list of "f104", "f309", "f505",
                    "f500", "commstat". Left out = every type.
  count             For "count": whole number, 1 or more.
  percent           For "share": above 0, up to 100.
  min_stations      For "share", optional: whole number, default 5.
  below_banner_text For "count" and "share", optional: when present, an
                    NTF with this text shows when it drops back below.
  conditions        Optional narrowing:
                      "f104_codes"         ["3"] Problems, ["2"]
                                           Degraded
                      "commstat_codes"     ["3"] Red, ["2"] Yellow
                      "f309_min_severity"  1 to 4
                      "f309_categories"    category codes, e.g. ["A"]
                      "f505_below"         a score, e.g. 1.5
                      "f500_questions"     e.g. ["water", "gasoline"]

Rules
  - A field that's left out means "no limit" (from, to) or "use the
    default" (display, minutes, cooldown_minutes, banner_text).
  - An empty list ("from": []) is an error, so it can't be confused with
    the callsign lists, where empty means nobody.
  - Fields AlertDisplay doesn't know are ignored and kept.
  - A trigger with an error is skipped, with a warning; the rest of the
    profile still works. An error in the profile itself (format,
    version, kind, profile_id, name, triggers) makes the whole profile
    unusable. Export as Group Profile refuses any error at all.
  - Not in version 1: regular expressions, band and SNR filters.

Example trigger
  {
    "id": "net-activation",
    "name": "Group net activation",
    "match": { "type": "contains", "text": ["NET ACTIVATED", "ACTIVATION"] },
    "from": ["KN4AM", "W4XYZ"],
    "to": ["{GROUP}"],
    "display": "ALR",
    "minutes": 120,
    "cooldown_minutes": 60,
    "banner_text": "NET ACTIVATION ({FROM}): {TEXT}"
  }

Example Status Board trigger
  {
    "id": "board-f104-problems",
    "name": "Station reports F!104 Problems",
    "source": "statusboard",
    "event": "newly_flagged",
    "types": ["f104"],
    "conditions": { "f104_codes": ["3"] },
    "display": "ALR",
    "minutes": 120,
    "banner_text": "{FROM} F!104: {STATUS} ({GRID}, {RANGE})"
  }


CONFIGURATION
-------------
AlertDisplay keeps its own files in ~/.config/alertdisplay/
(C:\Users\<user>\.config\alertdisplay\ on Windows):

  settings.json            Paths, notification mode, which profiles are
                           on and in what order, your trigger on/off
                           choices, callsign list checkboxes, Status
                           Board summary line settings, update check
                           on/off. Never shared.
  profiles/                One .json file per profile
  always_ignore.txt        Callsign lists
  ignore_all_except.txt
  active.json              The items showing now
  status.json              The monitor's state, for the settings window
  monitor.pid              Present while the monitor runs
  alertdisplay.log         The log
  clear.request,           Short-lived messages from the settings window
  stop.request,            to the monitor
  test.request

It reads, and never writes:
  JS8Call's DIRECTED.TXT
  ~/.config/sitrepmapper/config.json (DIRECTED.TXT path, station
  callsign and Station grid)
  IGNORE.TXT in SitRepMapper's program folder, if that box is checked
  ~/.config/sitrepmapper/statusboard.statusboard.json (the Status
  Board; SitRepMapper v1.18.0 or later)

It writes, in ~/.config/sitrepmapper/companions/:
  ALR.banner      The red ALERT strip
  NTF.banner      The notification strip
  ALR.indicator   The toolbar color block

Removing AlertDisplay
  1. Stop the monitor, and untick "Start the monitor automatically when
     I log in".
  2. Delete the AlertDisplay program folder and ~/.config/alertdisplay/.
  3. Delete ALR.banner, NTF.banner and ALR.indicator from
     ~/.config/sitrepmapper/companions/ if they're still there.


TROUBLESHOOTING
---------------
The block is GRAY with a dot
  The monitor isn't running, or stopped unexpectedly. Open the settings
  window and press Start Monitor. Check the Log tab for an "Error:" line.

The block is YELLOW
  The word says why ("No log", "No profile", "No calls", "List err");
  see WHAT SHOWS ON SITREPMAPPER. The top of the settings window and the
  Log tab give the full warning.

A message arrived but nothing showed
  Check the Log tab first. A matching message is always logged: shown,
  ignored because of a callsign list, or a repeat inside the cooldown.
  If the log says nothing about it, no trigger matched:
    - Is the profile turned on (Profiles tab), and the trigger on?
    - Paste the line from DIRECTED.TXT into Test a Message to see
      exactly what matches.
    - "Whole word" won't match inside a longer word, and "Starts with"
      only matches right after the TO callsign.
    - For a relayed message, From is the station after *DE*, not the
      relay.
    - A message sent before the monitor started isn't checked.

Too many false alarms
  Plain words (ALERT, EMERGENCY) turn up in chatter and automated
  weather posts. Use a code such as SITREP-ALERT, add a From or To
  filter, or make the trigger NTF instead of ALR. Turn off a group
  trigger you don't want with Turn On/Off; the coordinator can tighten
  it in the next release.

The red strip stays up after AlertDisplay was closed
  Closing the settings window doesn't stop the monitor, so alerts keep
  their normal timing. If the monitor crashed, the strip clears within 5
  minutes. To clear it now: python3 alertdisplay.py --clear

"This profile has errors" / Import refuses a file
  The box below the Profiles list (or the Import message) lists each
  problem. A group profile with errors should go back to the
  coordinator.

A trigger is skipped because it uses {MYCALL}
  Set your station callsign in SitRepMapper's settings. AlertDisplay
  picks it up within a few seconds.

"AlertDisplay monitor is already running."
  Only one monitor runs at a time. Use Stop Monitor, or
  python3 alertdisplay.py --stop, before starting another.

Status Board triggers do nothing / "Status Board file not found"
  The Status Board needs SitRepMapper v1.18.0 or later, run at least
  once. The Status Board tab says whether the file was found and how
  old it is. Also check that the trigger's report types and narrowing
  aren't stricter than you meant, and remember nothing fires for
  stations already flagged when the monitor starts. The Log tab records
  every Status Board event, including ones not shown.

The summary line ends "(stale)"
  SitRepMapper hasn't updated its Status Board for over 12 minutes,
  which usually means it isn't running. The line shows the last
  picture it saved.

A Status Board alert came up for a station I ignore
  Only Always Ignore and IGNORE.TXT apply to the Status Board. "Ignore
  all except" is for DIRECTED.TXT messages only.

"ModuleNotFoundError: No module named 'tkinter'"
  On Raspberry Pi OS, Ubuntu, or Debian:
      sudo apt install python3-tk


APPENDIX: HOW-TO GUIDES
-----------------------
Step-by-step walkthroughs for common tasks.

Setting Up a Coded Alert for Your Group
  The standard code is the group name and -ALERT: SITREP-ALERT for
  @SITREP (already in SitRepNet's profile), LCARES-ALERT for @LCARES.
  The group template (AlertDisplay_Group_Template.json) has this
  trigger ready. The coordinator of, say, @LCARES:
    1. Opens the group's own profile (made from the template) and the
       Group alert code trigger.
    2. Match: Whole word or phrase. Match text: LCARES-ALERT.
       To: {GROUP}.
    3. Display: ALR. Minutes shown: 120.
       Banner text: GROUP ALERT ({FROM}): {AFTER}
    4. Optionally, From: the stations allowed to send it.
    5. Ticks On, clicks OK, then Save.
    6. On Test a Message, tries
           KN4AM: @LCARES LCARES-ALERT STORM SURGE WARNING
       and checks the banner text reads
       "GROUP ALERT (KN4AM): STORM SURGE WARNING".
    7. Exports the profile (see the next guide) and sends it to members.
  To send an alert, a member types in JS8Call, to the group:
      @LCARES LCARES-ALERT STORM SURGE WARNING
  Sent with MSG ("@LCARES MSG LCARES-ALERT ...") it works too. Send
  the code over the air once as a test before relying on it.

Issuing a New Release of a Group Profile (coordinator)
    1. Profiles tab: open your own copy of the profile (the personal
       one you export from), make the changes and Save. If you only
       have the group profile itself, open it and click Edit Anyway...
       first.
    2. Select it and click Export as Group Profile...
    3. Check Author, Group callsign and Profile ID (it must be the same
       as last time); accept the offered Release (e.g. 2026-11-08.1) or
       type your own. Click Export... and choose where to save it.
    4. Send the file to members. Each member uses Import Group
       Profile...; it replaces their older release, and any triggers
       they turned on or off themselves stay that way.
  Never reuse or renumber a trigger's id: members' on/off choices are
  tied to it.

Changing a Group Profile for Yourself Only (member)
    1. Profiles tab: select the group profile, click Duplicate as
       Personal.
    2. Open the copy, edit it and Save.
    3. Turn the copy on and the group profile off.
  Note: your copy doesn't receive the group's later releases. To just
  silence one group trigger, use Turn On/Off in the group profile
  instead; that does carry over to new releases.

Getting an Alert When a Station Reports Problems
    1. Check the Status Board tab says the file was found and isn't
       stale (needs SitRepMapper v1.18.0 or later).
    2. Open a profile and click Add Status Board...
    3. Name: F!104 Problems. Event: Station newly flagged. Report types:
       F!104. Narrow it down: F!104 Problems only.
    4. Display: ALR. Banner text: {FROM} F!104: {STATUS} ({GRID}, {RANGE})
    5. OK, then Save. The alert clears by itself when that station
       sends a newer F!104 that isn't flagged.
  For widespread trouble, add another with Event: Flagged share
  reaches X%, Percent 33, Minimum stations 5.

Checking the Whole Chain Without Real Traffic
    1. Start the monitor.
    2. Test a Message: type a message that should match, click Show on
       SitRepMapper.
    3. Within about 10 seconds SitRepMapper shows it with "TEST:" at the
       start (on the red strip, with a red block, for an alert).
    4. It clears by itself after 2 minutes, or use Clear on the Active
       Alerts tab.


================================================================================
  End of AlertDisplay User Manual
  Designed and developed by Stephen Clay McGehee, KN4AM
  Coding done using Claude Code AI
  See SitRepNet.com for more information
================================================================================
