Skip to content

Commit 991e60c

Browse files
committed
Merge branch 'db-field-expiry'
2 parents 48e2c28 + c0d8957 commit 991e60c

12 files changed

Lines changed: 242 additions & 10 deletions

File tree

Source/Application/Engine.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,10 @@ void Engine::run(WebViewer *viewer, ControlCore *control)
202202
}
203203

204204
const int SLEEP = 50;
205+
#ifdef HASWEBVIEWER
206+
const int TICK_INTERVAL = 60;
207+
int tick_countdown = 0;
208+
#endif
205209
auto time_start = high_resolution_clock::now();
206210
auto time_timeout_start = time_start;
207211
auto time_last = time_start;
@@ -224,6 +228,18 @@ void Engine::run(WebViewer *viewer, ControlCore *control)
224228
if (iscallback)
225229
std::this_thread::sleep_for(std::chrono::milliseconds(SLEEP));
226230

231+
#ifdef HASWEBVIEWER
232+
// above the verbose/timeout shortcut below or it never runs by default
233+
if (--tick_countdown <= 0)
234+
{
235+
tick_countdown = TICK_INTERVAL * 1000 / SLEEP;
236+
237+
std::time_t tick_now = std::time(nullptr);
238+
for (auto v : viewers)
239+
v->tick(tick_now);
240+
}
241+
#endif
242+
227243
if (!oneverbose && !timeout)
228244
continue;
229245

Source/Application/WebViewer.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,14 @@ void WebViewer::attachEngine(AIS::Model &model, Connection<JSON::JSON> &json, De
511511
endAttach();
512512
}
513513

514+
void WebViewer::tick(std::time_t now)
515+
{
516+
std::lock_guard<std::recursive_mutex> lock(state_mtx);
517+
518+
for (auto &s : states)
519+
s->tick(now);
520+
}
521+
514522
void WebViewer::resetStatistics()
515523
{
516524
std::lock_guard<std::recursive_mutex> lock(state_mtx);
@@ -1082,6 +1090,9 @@ Setting &WebViewer::SetKey(AIS::Keys key, const std::string &arg)
10821090
case AIS::KEY_SETTING_CUTOFF:
10831091
settings.tracking.cutoff = Util::Parse::Integer(arg, 0, 10000);
10841092
break;
1093+
case AIS::KEY_SETTING_EXPIRE:
1094+
settings.tracking.expire_fields = Util::Parse::Switch(arg);
1095+
break;
10851096
case AIS::KEY_SETTING_SHARE_LOC:
10861097
settings.tracking.latlon_share = Util::Parse::Switch(arg);
10871098
frontend.setShareLoc(settings.tracking.latlon_share);

Source/Application/WebViewer.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,9 @@ class WebViewer : public IO::HTTPServer, public Setting
231231
void stopServing();
232232
// for good: unbinds, writes the statistics file and joins the server thread
233233
void shutdown();
234+
// periodic maintenance from the engine loop; handlers gate on the timestamp
235+
// and must return promptly, no blocking I/O
236+
void tick(std::time_t now);
234237
// drop the accumulated statistics, keeping the configuration (Android only)
235238
void resetStatistics();
236239
// Back to defaults, then the one setting the config file does not own. Taking

Source/JSON/KeyDefs.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ X(KEY_SETTING_GPS, "", "", "", "", "gps", "", "", "", nullptr)
112112
X(KEY_SETTING_GRDB, "", "", "", "", "grdb", "", "", "", nullptr)
113113
X(KEY_SETTING_GROUPS_IN, "", "", "", "", "groups_in", "", "", "", nullptr)
114114
X(KEY_SETTING_GZIP, "", "", "", "", "gzip", "", "", "", nullptr)
115+
X(KEY_SETTING_EXPIRE, "", "", "", "", "expire", "", "", "", nullptr)
115116
X(KEY_SETTING_SSL_VERIFY, "", "", "", "", "ssl_verify", "", "", "", nullptr)
116117
X(KEY_SETTING_HACKRF, "", "", "", "", "hackrf", "", "", "", nullptr)
117118
X(KEY_SETTING_HISTORY, "", "", "", "", "history", "", "", "", nullptr)

Source/Tracking/DB.cpp

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ void DB::setup()
3939
paths.resize(Npaths);
4040
hash_table.resize(HASH_SIZE);
4141

42+
last_sweep = std::time(nullptr);
43+
4244
first = Nships - 1;
4345
last = 0;
4446
count = 0;
@@ -839,7 +841,8 @@ bool DB::updateFields(const JSON::Member &p, const AIS::Message *msg, Ship &v, b
839841
v.received_stations = p.Get().getInt();
840842
break;
841843
case AIS::KEY_ALT:
842-
v.altitude = p.Get().getInt();
844+
if (msg->type() == 9)
845+
v.altitude = p.Get().getInt();
843846
break;
844847
case AIS::KEY_VIRTUAL_AID:
845848
v.setVirtualAid(p.Get().getBool());
@@ -991,7 +994,7 @@ bool DB::updateShip(const JSON::JSON &data, TAG &tag, Ship &ship)
991994

992995
ship.ppm = tag.ppm;
993996
ship.level = tag.level;
994-
ship.msg_type |= 1 << type;
997+
ship.markType(type);
995998

996999
if (msg->getChannel() >= 'A' && msg->getChannel() <= 'D')
9971000
ship.orOpChannels(1 << (msg->getChannel() - 'A'));
@@ -1029,11 +1032,13 @@ bool DB::updateShip(const JSON::JSON &data, TAG &tag, Ship &ship)
10291032
return positionUpdated;
10301033
}
10311034

1032-
static bool isBinaryContentKey(int key)
1035+
static bool isBinaryContent(const JSON::Member &p)
10331036
{
1034-
switch (key)
1037+
switch (p.Key())
10351038
{
10361039
case AIS::KEY_TEXT:
1040+
// getText trims the '@'/space padding, so a pure-padding broadcast is empty
1041+
return !p.Get().getString().empty();
10371042
case AIS::KEY_CREW_COUNT:
10381043
case AIS::KEY_PASSENGER_COUNT:
10391044
case AIS::KEY_SHIPBOARD_PERSONNEL_COUNT:
@@ -1109,7 +1114,7 @@ void DB::processBinaryMessage(const JSON::JSON &data, Ship &ship, bool &position
11091114
{
11101115
loc_lon = p.Get().getFloat();
11111116
}
1112-
else if (isBinaryContentKey(p.Key()))
1117+
else if (isBinaryContent(p))
11131118
{
11141119
has_content = true;
11151120
}
@@ -1269,6 +1274,23 @@ void DB::Receive(const JSON::JSON *data, int len, TAG &tag)
12691274
Send(data, len, tag);
12701275
}
12711276

1277+
void DB::tick(std::time_t now)
1278+
{
1279+
if (!expire_fields)
1280+
return;
1281+
1282+
std::lock_guard<std::mutex> lock(mtx);
1283+
1284+
if (now - last_sweep < TIME_HISTORY)
1285+
return;
1286+
1287+
last_sweep = now;
1288+
1289+
for (int ptr = first; ptr != -1; ptr = ships[ptr].incoming.next)
1290+
if (ships[ptr].mmsi)
1291+
ships[ptr].decayAndExpire();
1292+
}
1293+
12721294
bool DB::Save(std::ofstream &file)
12731295
{
12741296
std::lock_guard<std::mutex> lock(mtx);
@@ -1359,6 +1381,9 @@ bool DB::Load(std::ifstream &file)
13591381
// Not persisted; treat all loaded ships as having static data
13601382
temp_ships[i].last_static_signal = temp_ships[i].last_signal;
13611383

1384+
// Not persisted; stale records are left to the sweep
1385+
temp_ships[i].type_ttl = temp_ships[i].msg_type;
1386+
13621387
if (i > 0 && temp_ships[i].last_signal < previous_signal)
13631388
{
13641389
Error() << "DB: Ships not in chronological order at index " << i;

Source/Tracking/DB.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ class DB : public StreamIn<JSON::JSON>,
8181
int Npaths = Nships * 16;
8282
int HASH_SIZE = 8209;
8383

84+
bool expire_fields = false;
85+
std::time_t last_sweep = 0;
86+
8487
struct HashBucket
8588
{
8689
int first = -1;
@@ -133,7 +136,9 @@ class DB : public StreamIn<JSON::JSON>,
133136
std::mutex mtx;
134137

135138
void setup();
139+
void tick(std::time_t now);
136140
void setTimeHistory(int t) { TIME_HISTORY = t; }
141+
void setExpireFields(bool b) { expire_fields = b; }
137142
void setShareLatLon(bool b) { latlon_share = b; }
138143
bool getShareLatLon() { return latlon_share; }
139144

Source/Tracking/ReceiverTracker.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ void ReceiverTracker::applyConfig(const TrackingConfig &cfg, const AIS::Filter &
2626
ships.setMsgSave(cfg.msg_save);
2727
ships.setOwnMMSI(cfg.own_mmsi);
2828
ships.setTimeHistory(cfg.time_history);
29+
ships.setExpireFields(cfg.expire_fields);
2930
ships.setFilter(f);
3031

3132
// applied unconditionally: a config that drops "cutoff" must go back to the

Source/Tracking/ReceiverTracker.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ struct TrackingConfig
4444
bool use_GPS = true;
4545
uint32_t own_mmsi = 0;
4646
int time_history = 30 * 60;
47+
bool expire_fields = false;
4748
// 0 means "not configured": LONG_RANGE_CUTOFF_DEFAULT is used instead
4849
int cutoff = 0;
4950
};
@@ -106,6 +107,8 @@ struct ReceiverTracker
106107
void writeSummary(std::ostream &out);
107108

108109
// Ship data queries
110+
void tick(std::time_t now) { ships.tick(now); }
111+
109112
int getCount() { return ships.getCount(); }
110113
int getMaxCount() { return ships.getMaxCount(); }
111114
float getMsgRate() { return hist_second.getAverage(); }

Source/Tracking/Ships.cpp

Lines changed: 136 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ void Ship::reset()
2828
path_ptr = -1;
2929

3030
mmsi = count = msg_type = shiptype = group_mask = 0;
31+
type_ttl = 0;
3132
flags.reset();
3233

3334
heading = HEADING_UNDEFINED;
@@ -68,6 +69,130 @@ void Ship::reset()
6869
msg.clear();
6970
}
7071

72+
// Which fields each message type can refresh, indexed by type; F_SIGNAL is in every
73+
// row as reception data comes with every message. A missing field here expires
74+
// early, a spurious one never expires.
75+
static const uint32_t TYPE_FIELDS[MAX_MSG_TYPE + 1] = {
76+
0, // 0 does not exist
77+
F_SIGNAL | F_LATLON | F_SPEED_COG | F_HEADING | F_STATUS | F_MANEUVER | F_RECV_STATIONS, // 1 position report
78+
F_SIGNAL | F_LATLON | F_SPEED_COG | F_HEADING | F_STATUS | F_MANEUVER | F_RECV_STATIONS, // 2 position report
79+
F_SIGNAL | F_LATLON | F_SPEED_COG | F_HEADING | F_STATUS | F_MANEUVER | F_RECV_STATIONS, // 3 position report
80+
F_SIGNAL | F_LATLON | F_RECV_STATIONS, // 4 base station report
81+
F_SIGNAL | F_VOYAGE | F_STATIC, // 5 static and voyage
82+
F_SIGNAL | F_LATLON | F_OFF_POSITION | F_STATIC | F_VOYAGE, // 6 addressed binary, buoy/AtoN monitor payloads
83+
F_SIGNAL, // 7 binary acknowledge
84+
F_SIGNAL | F_STATIC | F_VOYAGE | F_OFF_POSITION, // 8 broadcast binary, inland static and AtoN monitor payloads
85+
F_SIGNAL | F_LATLON | F_SPEED_COG | F_ALTITUDE, // 9 SAR aircraft
86+
F_SIGNAL, // 10 UTC inquiry
87+
F_SIGNAL | F_LATLON | F_RECV_STATIONS, // 11 UTC response
88+
F_SIGNAL, // 12 addressed safety
89+
F_SIGNAL, // 13 safety acknowledge
90+
F_SIGNAL, // 14 broadcast safety
91+
F_SIGNAL, // 15 interrogation
92+
F_SIGNAL, // 16 assignment
93+
F_SIGNAL, // 17 DGNSS broadcast
94+
F_SIGNAL | F_LATLON | F_SPEED_COG | F_HEADING | F_COMM_CAP, // 18 class B position
95+
F_SIGNAL | F_LATLON | F_SPEED_COG | F_HEADING | F_STATIC, // 19 class B extended
96+
F_SIGNAL, // 20 link management
97+
F_SIGNAL | F_LATLON | F_OFF_POSITION | F_STATIC, // 21 aid to navigation
98+
F_SIGNAL, // 22 channel management
99+
F_SIGNAL, // 23 group assignment
100+
F_SIGNAL | F_STATIC, // 24 class B static
101+
F_SIGNAL | F_LATLON | F_OFF_POSITION | F_STATIC | F_VOYAGE, // 25 single slot binary, same payloads as 6/8
102+
F_SIGNAL | F_LATLON | F_OFF_POSITION | F_STATIC | F_VOYAGE | F_RECV_STATIONS, // 26 multiple slot binary, same payloads as 6/8
103+
F_SIGNAL | F_LATLON | F_SPEED_COG | F_STATUS, // 27 long range
104+
F_SIGNAL | F_LATLON | F_STATIC, // 28 AtoN report
105+
};
106+
107+
void Ship::decayAndExpire()
108+
{
109+
if (~type_ttl & msg_type)
110+
{
111+
uint32_t supported = 0;
112+
for (int t = 1; t <= MAX_MSG_TYPE; t++)
113+
if (type_ttl & (1 << t))
114+
supported |= TYPE_FIELDS[t];
115+
116+
clearFields(~supported);
117+
118+
msg_type = type_ttl;
119+
setType();
120+
}
121+
122+
type_ttl = 0;
123+
}
124+
125+
void Ship::clearFields(uint32_t doomed)
126+
{
127+
if (doomed & F_LATLON)
128+
{
129+
lat = LAT_UNDEFINED;
130+
lon = LON_UNDEFINED;
131+
distance = DISTANCE_UNDEFINED;
132+
angle = ANGLE_UNDEFINED;
133+
setApproximate(0);
134+
setValidated(0);
135+
setRAIM(0);
136+
setAssigned(0);
137+
}
138+
if (doomed & F_SPEED_COG)
139+
{
140+
speed = SPEED_UNDEFINED;
141+
cog = COG_UNDEFINED;
142+
}
143+
if (doomed & F_HEADING)
144+
heading = HEADING_UNDEFINED;
145+
if (doomed & F_STATUS)
146+
status = STATUS_UNDEFINED;
147+
if (doomed & F_MANEUVER)
148+
setManeuver(0);
149+
if (doomed & F_ALTITUDE)
150+
altitude = ALT_UNDEFINED;
151+
if (doomed & F_RECV_STATIONS)
152+
received_stations = RECEIVED_STATIONS_UNDEFINED;
153+
if (doomed & F_OFF_POSITION)
154+
setOffPosition(0);
155+
if (doomed & F_VOYAGE)
156+
{
157+
memset(destination, 0, sizeof(destination));
158+
month = ETA_MONTH_UNDEFINED;
159+
day = ETA_DAY_UNDEFINED;
160+
hour = ETA_HOUR_UNDEFINED;
161+
minute = ETA_MINUTE_UNDEFINED;
162+
draught = DRAUGHT_UNDEFINED;
163+
}
164+
if (doomed & F_STATIC)
165+
{
166+
memset(shipname, 0, sizeof(shipname));
167+
memset(callsign, 0, sizeof(callsign));
168+
memset(vendorid, 0, sizeof(vendorid));
169+
memset(vin, 0, sizeof(vin));
170+
unit_model = unit_serial = -1;
171+
IMO = IMO_UNDEFINED;
172+
shiptype = 0;
173+
to_port = to_bow = to_starboard = to_stern = DIMENSION_UNDEFINED;
174+
setDTE(0);
175+
setVirtualAid(0);
176+
}
177+
if (doomed & F_COMM_CAP)
178+
{
179+
setCSUnit(0);
180+
setDisplay(0);
181+
setDSC(0);
182+
setBand(0);
183+
setMsg22(0);
184+
}
185+
if (doomed & F_SIGNAL)
186+
{
187+
ppm = PPM_UNDEFINED;
188+
level = LEVEL_UNDEFINED;
189+
setRepeat(0);
190+
memset(country_code, 0, sizeof(country_code));
191+
clearOpChannels();
192+
msg.clear();
193+
}
194+
}
195+
71196
std::string getSprite(const Ship *ship)
72197
{
73198
std::string shipofs = (ship->speed != SPEED_UNDEFINED && ship->speed > 0.5) ? "<y>88</y><w>20</w><h>20</h>" : "<y>68</y><w>20</w><h>20</h>";
@@ -168,7 +293,16 @@ int Ship::getMMSItype()
168293
{
169294
return MMSI_SARTEPIRB;
170295
}
171-
if (msg_type & ATON_MASK || (mmsi >= 990000000 && mmsi <= 999999999))
296+
// the number outranks the message types: a bad decode must not reclassify a station
297+
if (mmsi >= 990000000 && mmsi <= 999999999)
298+
{
299+
return MMSI_ATON;
300+
}
301+
if (mmsi < 9000000)
302+
{
303+
return MMSI_BASESTATION;
304+
}
305+
if (msg_type & ATON_MASK)
172306
{
173307
return MMSI_ATON;
174308
}
@@ -180,7 +314,7 @@ int Ship::getMMSItype()
180314
{
181315
return MMSI_CLASS_B;
182316
}
183-
if (msg_type & BASESTATION_MASK || (mmsi < 9000000))
317+
if (msg_type & BASESTATION_MASK)
184318
{
185319
return MMSI_BASESTATION;
186320
}

0 commit comments

Comments
 (0)