202 Commits

Author SHA1 Message Date
xbgmsharp
8c777cd028 Update frontend pre-release 0.8.0 2023-11-24 12:06:04 +01:00
xbgmsharp
cfe3105f87 Update test to math latest release 2023-11-24 12:05:07 +01:00
xbgmsharp
5af24a1878 Release 0.5.0 2023-11-24 12:00:35 +01:00
xbgmsharp
0aae8d002b Update process_logbook enforce more than 2 metrics to be consider a log 2023-11-24 12:00:10 +01:00
xbgmsharp
22c69a2fd9 Update postgrest PGRST_DB_POOL to the connections limit of api_anonymous role 2023-11-23 17:42:34 +01:00
xbgmsharp
c5f1b85a16 Update openapi documentation 2023-11-23 15:14:58 +01:00
xbgmsharp
0157fe12e5 Update ERD schema 2023-11-23 15:14:45 +01:00
xbgmsharp
ead2b99e7f Update comments on api schema 2023-11-23 15:02:06 +01:00
xbgmsharp
711d5a0d40 Update auth.accounts, add public_id for future use. 2023-11-23 10:27:34 +01:00
xbgmsharp
7e52065ef8 Update metadata trigger, Ignore and overwrite the time sent be the vessel, fix #3 2023-11-23 10:24:16 +01:00
xbgmsharp
f65873db81 Update README 2023-11-21 20:44:36 +01:00
xbgmsharp
347299d76e Add missing description to python function 2023-11-21 20:44:09 +01:00
xbgmsharp
effeb29915 Update test to logs_view change 2023-11-20 21:22:25 +01:00
xbgmsharp
9329a6d04b Add triiger on vessel creation to set the public_vessel name 2023-11-20 21:16:17 +01:00
xbgmsharp
70be4fb295 Update logs_view, show only processed logbook 2023-11-20 21:15:37 +01:00
xbgmsharp
5960447297 Update/fix trigger, remove the actual data 2023-11-20 21:15:15 +01:00
xbgmsharp
f240222b98 Update test to match change from SERIAL to IDENTITY, fix d76964f3db 2023-11-19 22:18:07 +01:00
xbgmsharp
0218f2fa73 Follow best practive don't use timestamp(0) 2023-11-19 22:17:47 +01:00
xbgmsharp
79a96c7556 Update tests, debug mermerd Something went wrong! with github action 2023-11-19 21:50:40 +01:00
xbgmsharp
21f96483f5 Follow best practive don't use timestamp(0)
https://wiki.postgresql.org/wiki/Don%27t_Do_This#Don.27t_use_timestamp.280.29_or_timestamptz.280.29
2023-11-19 21:49:19 +01:00
xbgmsharp
4c6d6290f0 Update schemalint to add more best practice support 2023-11-19 21:40:58 +01:00
xbgmsharp
dc02dc886d Update SERIAL to IDENTITY test 2023-11-19 21:38:57 +01:00
xbgmsharp
6355f98792 Update openapi documentation 2023-11-19 21:38:22 +01:00
xbgmsharp
7543c93dcf Update ERD schema 2023-11-19 21:38:07 +01:00
xbgmsharp
d76964f3db Update test to match change from SERIAL to IDENTITY 2023-11-19 21:36:15 +01:00
xbgmsharp
de651ea7ab Replace type SERIAL by IDENTITY as per best practice.
https://wiki.postgresql.org/wiki/Don%27t_Do_This#Don.27t_use_serial
2023-11-19 21:34:20 +01:00
xbgmsharp
fddd3df05e Replace type TIMESTAMP WITHOUT TIME ZONE by TIMESTAMPZ with timezone forcing UTC as per best practice. 2023-11-19 21:10:28 +01:00
xbgmsharp
472131efbd refactor metrics_trigger_fn, set previous_metric as record instead of individual value 2023-11-19 18:03:54 +01:00
xbgmsharp
1f7bb433e2 Update gpx moorages export, make urk link dynamic 2023-11-18 23:03:42 +01:00
xbgmsharp
c4fa9f5512 Update reverse geo loc, avoid loop 2023-11-18 23:03:07 +01:00
xbgmsharp
b005f592e9 Update project description 2023-11-18 22:48:48 +01:00
xbgmsharp
6cc13313f1 Update tests to match the new anonymous access feature 2023-11-18 21:38:55 +01:00
xbgmsharp
fdb466abde Refactor anonymous access, update public.check_jwt, api.ispublic_fn to allow anoymous base on public vessel name 2023-11-18 21:37:20 +01:00
xbgmsharp
5f0adb67c8 Update openapi 2023-11-18 21:34:32 +01:00
xbgmsharp
2e170c5480 iUpdate ERD 2023-11-18 21:34:22 +01:00
xbgmsharp
5dd2875b91 Update README 2023-11-18 13:34:48 +01:00
xbgmsharp
ee131e0e70 Update metrics_trigger_fn, fix stay postprocessing, only when gone. 2023-11-15 00:16:25 +01:00
xbgmsharp
6426e14d54 Update openapi documentation 2023-11-14 23:56:54 +01:00
xbgmsharp
8fff17dee3 Update process_stay_queue_fn, process stays only when gone 2023-11-14 23:51:23 +01:00
xbgmsharp
eb8ba54230 Update tests to match refactore of logs,stays,moorages. Draft anonymous access 2023-11-14 23:49:43 +01:00
xbgmsharp
f9ed13761c Update ERD mermiad diagram 2023-11-14 23:49:30 +01:00
xbgmsharp
e1e7da779e Update tests to match refactor of Logs,Stays,Moorages
Update dynamic openapi documentation
Add dynamic mermaid schema documentation
2023-11-14 23:28:20 +01:00
xbgmsharp
c879c4bdab Refactor Logs,Moorages,Stays
Update Logs,Moorages,Stays view
Add public.process_lat_lon_fn
Add public.moorage_update_trigger_fn
Add public.moorage_delete_trigger_fn
Update debug on metrics_trigger_fn
Deprecated public.process_moorage_queue_fn
2023-11-14 23:24:30 +01:00
xbgmsharp
e5f2469358 Add overpass-turbo api to improve geo location detection 2023-11-14 23:20:58 +01:00
xbgmsharp
480409de12 Update ERD schema, add mermerd config 2023-11-12 15:21:45 +01:00
xbgmsharp
9d8a7294e0 Update ERD diagram 2023-11-12 14:55:16 +01:00
xbgmsharp
e3ae6b4243 Update ERD Mermaid diagram 2023-11-12 14:53:24 +01:00
xbgmsharp
268ce5b908 Update ERD README 2023-11-12 14:51:21 +01:00
xbgmsharp
ce55a58c87 Update diagram layout 2023-11-12 14:50:30 +01:00
xbgmsharp
14e2103e0f Update Mermaid 2023-11-12 13:09:25 +01:00
xbgmsharp
8025fc4d52 Update ERD doc 2023-11-12 13:06:09 +01:00
xbgmsharp
117bdd2e3f Update ERD diagram to be dynamic 2023-11-12 13:01:46 +01:00
xbgmsharp
b37c33bccb Update api.delete_logbook_fn, add missing query 2023-11-11 11:32:29 +01:00
xbgmsharp
2507545d3f Update anonymous access, add check to allow public access if header x-public-id is present with a base64 value mapping a path and an id. 2023-11-02 21:42:03 +01:00
xbgmsharp
b6ef06d382 Add unit test for public access 2023-11-02 21:39:37 +01:00
xbgmsharp
30de9b76af Update test to prepare for anonymous access 2023-11-02 21:38:25 +01:00
xbgmsharp
8f1558f436 Update OpenAPI doc 2023-11-02 21:37:15 +01:00
xbgmsharp
636fae7ce6 Update lint format 2023-11-02 21:36:53 +01:00
xbgmsharp
8197a26c49 Release 0.4.1 2023-10-31 10:18:02 +01:00
xbgmsharp
ba3b213423 Update SQL Uppercase type 2023-10-30 21:17:21 +01:00
xbgmsharp
0be57a4e70 Update role, add iew select for user_role 2023-10-30 21:16:44 +01:00
xbgmsharp
80163d3fe2 Update schemalint, disable tests, most require large review for future reference 2023-10-30 11:59:24 +01:00
xbgmsharp
c8795b15f3 Update test versions, latest PostgREST 2023-10-29 23:08:21 +01:00
xbgmsharp
e8c0ea5c94 revert logbook naming 2023-10-29 22:54:53 +01:00
xbgmsharp
38ad6084bb Updte openAPI 2023-10-29 22:54:12 +01:00
xbgmsharp
c726187b4d Update explore view 2023-10-29 22:38:45 +01:00
xbgmsharp
3eafa2e13f Update export functiond to order by date rether than id.
Update delete logbook to return a boolean
2023-10-29 21:31:15 +01:00
xbgmsharp
d13f096d4f Update process_logbook_queue_fn, remove the gpx handler 2023-10-29 18:44:43 +01:00
xbgmsharp
e2e37e1f01 Add explore view 2023-10-29 18:44:03 +01:00
xbgmsharp
3bbe309de3 Add delete logbook and dependency stays 2023-10-29 18:43:32 +01:00
xbgmsharp
2be7c787dd Remove duplicated 2023-10-28 22:28:14 +02:00
xbgmsharp
9aecda4752 Update reverse_geocode_py_fn, improve location detection, ignore tag road or highway. Recursive over lower zoom level. 2023-10-28 21:55:29 +02:00
xbgmsharp
9532075bc4 Update frontend to release 0.0.7 2023-10-25 22:18:56 +02:00
xbgmsharp
5996b4d483 Upgrade to the latest version and prepare for next release 2023-10-25 12:40:49 +02:00
xbgmsharp
fdd6fc18e1 Update logbook tests, no more track_gpx field 2023-10-25 12:08:11 +02:00
xbgmsharp
af3866fafe Add new test for export logbooks endpoint 2023-10-25 12:07:07 +02:00
xbgmsharp
53daaa9947 Update vessel_fn to handle empty vessel name from signalk in api.metadata 2023-10-25 09:28:10 +02:00
xbgmsharp
3fed9e0b6a Update public.logbook_update_geojson_fn, expose more data
Update public.logbook_metrics_dwithin_fn, expend large zone for invalid logbook, increase to 15metres.
Disable public.logbook_update_gpx_fn, back to using api.export_logbook_gpx_fn.
Add public.get_app_url_fn, allow limited access to the ap url settings.
2023-10-25 09:27:48 +02:00
xbgmsharp
f3168542fd Update api.timelapse_fn, use track_geom as geometry instead of track_geojson as geojson. x10 faster.
Update api.export_logbook_geojson_fn, output as JSONB as per best practice.
Update api.export_logbook_gpx_fn, dynamic from track_geojson on 'geometry'->>'type' = 'Point'.
Add api.export_logbooks_gpx_fn, export multiple logs in a GPX format
Add api.export_logbooks_kml_fn, export multiple logs in a KML format
2023-10-25 09:27:03 +02:00
xbgmsharp
d266485fef Update table api.logbook, remove tack_gpx colunm, update track_geojson to jsonb type 2023-10-25 09:25:51 +02:00
xbgmsharp
8738becd82 Update OpenAPI documentation 2023-10-25 09:24:27 +02:00
xbgmsharp
ad43ca6629 Update auth.accounts wiht comments 2023-10-23 21:39:53 +02:00
xbgmsharp
9368878963 Update .gitignore 2023-10-22 20:46:20 +02:00
xbgmsharp
496491a43a Update README 2023-10-22 20:46:08 +02:00
xbgmsharp
7494b39abc Update api views with standard name-casing 2023-10-22 19:50:13 +02:00
xbgmsharp
74426a75f8 Update maint tests, cleanup 2023-10-22 19:44:34 +02:00
xbgmsharp
9bac88a8cc Update main tests, add openapi.json update 2023-10-22 19:43:46 +02:00
xbgmsharp
c0af53155c Update tests to match new public_id colunm for auth.accounts 2023-10-22 19:43:06 +02:00
xbgmsharp
e0aa6a4d0e Udpate README 2023-10-22 19:39:39 +02:00
xbgmsharp
2425b674f7 Update auth.accounts, remove unused userid, add public_id integer for anonymous and grafana orgId references 2023-10-22 19:34:41 +02:00
xbgmsharp
b7a1462ec6 Update permissions 2023-10-22 19:28:03 +02:00
xbgmsharp
a31d857a6e Update open API documentation 2023-10-22 19:25:58 +02:00
xbgmsharp
dbeb64c0dc Add schemalint github actioni for best practice reference 2023-10-22 19:24:45 +02:00
xbgmsharp
229c219751 Update metrics_trigger_fn, Ignore metric entry if latitude,longitude are equal,l
Update api.logbook and api.stays tables, update type to text instead of varchar
2023-10-22 18:43:54 +02:00
xbgmsharp
3216ffe42c Update cron_process_no_activity_fn, check for vessel with no activity for more than 200 days then send notification
Add cron_process_deactivated_fn, check for inactivity for more than 1 year to send notification and delete data
2023-10-18 23:25:21 +02:00
xbgmsharp
e2e3e5814e Update api.vessel_fn expose data from the signalk rather than from user input
Update api.settings_fn expose accounts.public_id in settings
Update api.eventlogs_view, cleanup formating
Add api.ispublic_fn, check is a page is publicly accessible from user preferences
2023-10-18 23:21:22 +02:00
xbgmsharp
5f709eb71e Update mocha http tests, add export_logbook_kml_fn basic test 2023-10-18 11:45:39 +02:00
xbgmsharp
d5bf36a85c Update message template for notifications 2023-10-18 11:44:54 +02:00
xbgmsharp
90d48c0c52 Update cron job, fix job details cleanup 2023-10-16 21:46:09 +02:00
xbgmsharp
62707aa86c Update export_logbook_gpx_fn, update comment 2023-10-16 16:31:42 +02:00
xbgmsharp
ac187a1480 update kml export, fix linestrng xml export 2023-10-16 12:21:25 +02:00
xbgmsharp
7b0bf7494f Udpate get_user_settings_from_vesselid, optimize query use citext extensions.
Update public.check_jwt, fix typo..
2023-10-16 11:53:44 +02:00
xbgmsharp
c64219e249 Fix init export_gpx2 2023-10-16 00:55:33 +02:00
xbgmsharp
2127dd7fcb Update github actions test, increase delay to allow the db to be loaded from initdb 2023-10-16 00:48:42 +02:00
xbgmsharp
2a583b94dc Update api.export_logbook_kml_fn, allow export in SML content-type 2023-10-16 00:37:16 +02:00
xbgmsharp
147d9946c3 Update connections limit to 20 for anonymous 2023-10-16 00:29:16 +02:00
xbgmsharp
993cfaeaff Update cron jobs 2023-10-13 15:59:21 +02:00
xbgmsharp
3e70283221 Update public.logbook_update_geojson_fn formating 2023-10-13 15:58:40 +02:00
xbgmsharp
0697acb940 Update message template 2023-10-13 15:40:15 +02:00
xbgmsharp
8ca4d03649 Update permisions for user_role and grafana 2023-10-13 15:39:18 +02:00
xbgmsharp
7a465ff532 Update grafana home dashboard with geomap 2023-10-13 14:58:41 +02:00
xbgmsharp
96dce86678 Update api.export_logbook_kml_fn but still not working in REST 2023-10-11 17:33:55 +02:00
xbgmsharp
8dd827f70d Add new SQL tests for update_logbook_observations_fn 2023-10-11 17:31:43 +02:00
xbgmsharp
572f0cd19d Fix job_run_details_cleanup_fn 2023-10-11 17:20:08 +02:00
xbgmsharp
047f243758 Fix api.update_logbook_observations_fn. 2023-10-11 17:19:10 +02:00
xbgmsharp
5c494896c6 Increase default number of connection ofr grafana_auth 2023-10-11 17:18:41 +02:00
xbgmsharp
b7e717afbc Update notificaiton jobs, fix sql query for no_vessel,no_metadata,no_activitye 2023-10-10 13:51:51 +02:00
xbgmsharp
2f3912582a Update api.export_logbook_kml_fn fix export 2023-10-09 20:38:24 +02:00
xbgmsharp
f7b9a54a71 Update KML export with basic LineString support 2023-10-09 20:30:27 +02:00
xbgmsharp
4e554083b0 Add Row level security to vessel_view. Clean up code, unsing pg15 RLS can be apply to view 2023-10-09 16:27:19 +02:00
xbgmsharp
69b6490534 Update api.vessels_view LIMIT to last metrics.
Add Rowlevel security measures to view
2023-10-09 16:26:02 +02:00
xbgmsharp
8b336f6f9b Add explicit schema when public 2023-10-09 16:22:30 +02:00
xbgmsharp
ef5868d412 Update public.logbook_update_gpx_fn to be display in order
Update public.logbook_update_geojson_fn formating
2023-10-09 16:13:56 +02:00
xbgmsharp
ce532bbb4d Fix typo KLM KLM file extensions 2023-10-09 16:13:09 +02:00
xbgmsharp
66999ca9bb Update api.timelapse_fn, order logs by id to be disaply in order
Add draft support for KML export
2023-10-09 16:10:25 +02:00
xbgmsharp
65d0a6fe4b Update frontend to latest dev 2023-10-06 00:22:32 +02:00
xbgmsharp
f7724db62a Update home dashboard 2023-10-06 00:05:37 +02:00
xbgmsharp
01c20651a4 update grafana 2023-10-05 23:32:24 +02:00
xbgmsharp
57d38ba893 Update reverse geocode, fix error on invalid geocode 2023-10-05 00:42:07 +02:00
xbgmsharp
b817a837d0 Release 0.3.0 2023-10-04 16:59:49 +02:00
xbgmsharp
e1fccabba5 Revert notification, send reminders every Sunday 2023-10-04 16:59:20 +02:00
xbgmsharp
b386e307f9 Update tests output, release 0.3.0 and latest version of PostgREST 2023-10-04 16:54:44 +02:00
xbgmsharp
53b25e1656 Add public.delete_vessel_fn, delete all data received from a vessel 2023-10-04 16:40:45 +02:00
xbgmsharp
9c7301deac Update login fn to return 401 Unauthorized vs 403 Forbidden 2023-10-04 16:39:40 +02:00
xbgmsharp
0f08667d3f Update Notifications/Reminders for no vessel & no metadata & no activity to once month 'At 08:01 on day-of-month 6 and on Sunday.' 2023-10-03 22:30:38 +02:00
xbgmsharp
baea4031b8 Update tests to match language check 2023-10-02 21:41:02 +02:00
xbgmsharp
3dcae9199f Update reverse code, enforce english language result 2023-10-02 21:40:44 +02:00
xbgmsharp
e8259d231e Update tests, pg_language change 2023-10-01 22:55:13 +02:00
xbgmsharp
dd81d49895 Update ERD api and public schema change 2023-10-01 22:51:56 +02:00
xbgmsharp
b861e4151c Update open API 2023-10-01 22:21:01 +02:00
xbgmsharp
42cfa34de8 Update tests, update timescale version 2023-10-01 22:13:54 +02:00
xbgmsharp
fa48d23b1a Add new fn for new cron schedule jobs no_vessel,no_metadata,no_activity. Update logging, fix typo 2023-10-01 22:12:15 +02:00
xbgmsharp
a28ea4631b Add new weekly cron notification for no_vessel,no_metadata,no_activity 2023-10-01 22:11:10 +02:00
xbgmsharp
1793dba64f Add new email templates, for no vessel created, no vessel connected, no recent vessel data. 2023-10-01 22:09:50 +02:00
xbgmsharp
b8c70f43b9 Add new helper fn, isdouble 2023-10-01 13:47:45 +02:00
xbgmsharp
be5c3e9a6f Update api.metrics, remove CONSTRAINT on lat and lon to ingore silently invalid value 2023-10-01 13:46:59 +02:00
xbgmsharp
427d30681e Update vessels views and fn, add plugin version, offline status and duration 2023-09-29 22:42:43 +02:00
xbgmsharp
3130394ab0 Update api.export_moorages_geojson_fn, add stay code 2023-09-29 22:40:52 +02:00
xbgmsharp
4e1e890ef7 Fix reset password, ambigouis colunm 2023-09-24 15:39:28 +02:00
xbgmsharp
f46787ca72 Update API documentation 2023-09-22 12:23:21 +02:00
xbgmsharp
6bb3fd7243 Update tests to match github actions results 2023-09-22 12:12:03 +02:00
xbgmsharp
27ab0d590f Update tests to match github actions results 2023-09-22 12:05:34 +02:00
xbgmsharp
e295380bcf Update stay_at table, fix typo in description 2023-09-22 12:05:04 +02:00
xbgmsharp
f9cebf1bda Update tests results 2023-09-22 11:08:17 +02:00
xbgmsharp
51bfc3ca9a Update github actions, Revert previous, extra second in durattion! 2023-09-22 10:52:18 +02:00
xbgmsharp
7d3667726b Update tests restult to match github actions, internval have a 1S extra!?!, 2023-09-21 23:32:39 +02:00
xbgmsharp
5ec987e6bc Update test to math github actions rego reverse result 2023-09-21 23:26:06 +02:00
xbgmsharp
cbef039a26 Update tests results, new interval output style iso, new reverse_geo_py output with jsonb 2023-09-21 23:18:51 +02:00
xbgmsharp
23780e2c01 Update logbook,stays,moorage process functions to match reverse_geocode_py_fn jsonb ouput 2023-09-21 23:17:25 +02:00
xbgmsharp
a1306f06e2 Update reverse_geocode_py_fn, output jsonb to add country_code filed 2023-09-21 23:16:42 +02:00
xbgmsharp
ed90fdd01d Add debug in reverse_geocode_py, github action return different result 2023-09-20 16:56:44 +02:00
xbgmsharp
23bce1ad26 Update test result 2023-09-20 16:56:09 +02:00
xbgmsharp
093992443b Udpate tests results 2023-09-20 00:22:10 +02:00
xbgmsharp
99dea0dbc8 Add default database date and interval style, set interval style to iso_8601 format 2023-09-19 23:29:36 +02:00
xbgmsharp
7edd2be1fd Update api_fn, Add api.stats_stays_fn, Update api.stats_logs_fn, Add logs_by_day_fn 2023-09-19 23:29:15 +02:00
xbgmsharp
e8a899f36c Update metrics_trigger_fn, Add validation check for speedOverGround.
Ignore if speedOverGround is over 40.
2023-09-19 23:29:00 +02:00
xbgmsharp
35940917e0 Update api.moorages_view and api.moorage_view, add stay code and stay description in web view 2023-09-14 09:52:19 +02:00
xbgmsharp
ecb6e666d2 Update api.moorages_view 2023-09-13 21:58:51 +02:00
xbgmsharp
7b11de9d0d Add support for logbook observations jsonb 2023-09-13 21:57:38 +02:00
xbgmsharp
788b6f160b Update Grafana role with monitoring viewse 2023-09-13 21:56:26 +02:00
xbgmsharp
cad4d38595 Update README 2023-08-26 13:56:47 +02:00
xbgmsharp
1e177dd770 Update 2e2 frontend tests 2023-08-25 11:23:09 +02:00
xbgmsharp
96e91784c5 More github actions e2e testing 2023-08-25 11:14:10 +02:00
xbgmsharp
d5330ca482 Update github actions front-end e2e tests 2023-08-25 11:04:05 +02:00
xbgmsharp
4e304bfc53 Update README 2023-08-25 11:03:48 +02:00
xbgmsharp
b237d91990 Update github actions frontend tests 2023-08-23 13:00:36 +02:00
xbgmsharp
6ae7591f3d Update tests reduce check to postgres version to remove architecture from check 2023-08-23 12:48:49 +02:00
xbgmsharp
3a4aadc81a Update postgrest version parsing 2023-08-23 12:46:35 +02:00
xbgmsharp
c8c10dd51c Release 0.2.3 2023-08-23 12:30:55 +02:00
xbgmsharp
1bfea2ade5 Tests, add missing package.json 2023-08-23 12:29:11 +02:00
xbgmsharp
5105322eed Add unit tests mocha and sql 2023-08-23 12:23:51 +02:00
xbgmsharp
2d3c531960 Update api.logbook comment extra column 2023-08-22 17:00:43 +02:00
xbgmsharp
f37156f15c update github actions 2023-08-22 16:59:50 +02:00
xbgmsharp
11e0493964 Update README, openapi link 2023-08-22 16:59:40 +02:00
xbgmsharp
eee9ea6065 Update logbook extra json obj, add observations (seaState,cloudCoverage,Visibility) default values 2023-08-22 16:41:44 +02:00
xbgmsharp
b2f1e5e0e9 Update versions, add postgrest version 2023-08-22 16:41:01 +02:00
xbgmsharp
2a77216ef6 Update API schema comment for OpenAPI documentation. 2023-08-21 17:13:32 +02:00
xbgmsharp
eee862900d Update openapi documentation. 2023-08-21 15:24:43 +02:00
xbgmsharp
dc54d0c5e3 Update README 2023-08-21 15:11:07 +02:00
xbgmsharp
2684c83ce8 Update README 2023-08-21 15:09:20 +02:00
xbgmsharp
412a6e8a58 Add openapi documentation 2023-08-20 11:19:53 +02:00
xbgmsharp
f0198aeb3e Update github actions 2023-08-18 01:49:08 +02:00
xbgmsharp
2a8f74a62f Update github actions 2023-08-18 01:42:24 +02:00
xbgmsharp
0eda59d68a Update github actions 2023-08-18 01:36:53 +02:00
xbgmsharp
652b72c274 Update github actions 2023-08-18 01:26:53 +02:00
xbgmsharp
3b798c99c4 Update github actions e2e tests 2023-08-18 00:56:58 +02:00
xbgmsharp
4b6eeefdba Update web_dev and web_tests containers for e2e testings 2023-08-18 00:56:01 +02:00
xbgmsharp
306623a55a Add new API function, api.stats_logs_fn, export logs statistics 2023-08-18 00:54:58 +02:00
xbgmsharp
5230a83833 Update logbook table, add comment on column 2023-08-18 00:54:11 +02:00
xbgmsharp
96f80b9584 Update export logbook gpx function, move the function inside the logbook cron process workflow rather than directly on an API call. 2023-08-15 23:18:07 +02:00
xbgmsharp
c63bf63308 Update api.timelapse_fn, return only logbook with geojson data. 2023-08-15 10:16:23 +02:00
xbgmsharp
0f78d56b37 Ensure moorages view and function have a valid geographic point 2023-08-14 22:50:35 +02:00
67 changed files with 11252 additions and 3012 deletions

55
.github/workflows/db-lint.yml vendored Normal file
View File

@@ -0,0 +1,55 @@
name: Linting rules on database schema.
on:
pull_request:
paths:
- 'initdb/**'
branches:
- 'main'
push:
branches:
- 'main'
paths:
- 'initdb/**'
tags:
- "*"
workflow_dispatch:
jobs:
schemalint:
name: schemalint
runs-on: ubuntu-latest
steps:
- name: Check out the source
uses: actions/checkout@v3
- name: Set env
run: cp .env.example .env
- name: Pull Docker images
run: docker-compose pull db api
- name: Run PostgSail Database & schemalint
# Environment variables
env:
# The hostname used to communicate with the PostgreSQL service container
PGHOST: localhost
PGPORT: 5432
PGDATABASE: signalk
PGUSER: username
PGPASSWORD: password
run: |
set -eu
source .env
docker-compose stop || true
docker-compose rm || true
docker-compose up -d db && sleep 30 && docker-compose up -d api && sleep 5
docker-compose ps -a
echo ${PGSAIL_API_URL}
curl ${PGSAIL_API_URL}
npm i -D schemalint
npx schemalint
- name: Show the logs
if: always()
run: |
docker-compose logs

View File

@@ -4,6 +4,7 @@ on:
pull_request:
paths:
- 'initdb/**'
- 'tests/**'
branches:
- 'main'
push:
@@ -11,6 +12,9 @@ on:
- 'main'
paths:
- 'initdb/**'
- 'tests/**'
tags:
- "*"
workflow_dispatch:
jobs:
@@ -27,6 +31,9 @@ jobs:
- name: Pull Docker images
run: docker-compose pull db api
- name: Build Docker images
run: docker-compose -f docker-compose.dev.yml -f docker-compose.yml build tests
- name: Install psql
run: sudo apt install postgresql-client
@@ -44,7 +51,7 @@ jobs:
source .env
docker-compose stop || true
docker-compose rm || true
docker-compose up -d db && sleep 15 && docker-compose up -d api && sleep 5
docker-compose up -d db && sleep 30 && docker-compose up -d api && sleep 5
docker-compose ps -a
echo ${PGSAIL_API_URL}
curl ${PGSAIL_API_URL}
@@ -54,7 +61,12 @@ jobs:
echo "Test PostgSail version"
psql -c "SELECT value FROM app_settings WHERE name = 'app.version';"
echo "Test PostgSail Unit Test"
docker-compose -f docker-compose.dev.yml -f docker-compose.yml up tests
docker compose -f docker-compose.dev.yml -f docker-compose.yml up tests --abort-on-container-exit --exit-code-from tests
if [ $? != 0 ];
then
echo "Error running db-tests"
exit 1
fi
- name: Show the logs
if: always()
run: |

View File

@@ -11,6 +11,8 @@ on:
- 'main'
paths:
- 'frontend/**'
tags:
- "*"
workflow_dispatch:
jobs:
@@ -19,14 +21,19 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
with:
submodules: 'true'
- name: Set env
run: cp .env.example .env
- name: Pull Docker images
run: docker-compose pull db api web
run: docker compose -f docker-compose.dev.yml -f docker-compose.yml pull db api web_tests
- name: Run PostgSail Web test
- name: Build Docker images
run: docker compose -f docker-compose.dev.yml -f docker-compose.yml build web_dev
- name: Run PostgSail Web tests
# Environment variables
env:
# The hostname used to communicate with the PostgreSQL service container
@@ -40,12 +47,19 @@ jobs:
source .env
docker-compose stop || true
docker-compose rm || true
docker-compose up -d db && sleep 15 && docker-compose up -d api && sleep 5
docker-compose up -d db && sleep 30 && docker-compose up -d api && sleep 5
docker-compose ps -a
echo "Test PostgSail Web Unit Test"
docker-compose up -d web && sleep 5
docker-compose ps -a
docker compose -f docker-compose.dev.yml -f docker-compose.yml up -d web_dev && sleep 100
docker compose -f docker-compose.dev.yml -f docker-compose.yml logs web_dev
docker compose ps -a
curl http://localhost:8080/
docker compose -f docker-compose.dev.yml -f docker-compose.yml up web_tests --abort-on-container-exit --exit-code-from web_tests
if [ $? != 0 ];
then
echo "Error running frontend-tests"
exit 1
fi
- name: Show the logs
if: always()
run: |

View File

@@ -11,6 +11,8 @@ on:
- 'main'
paths:
- 'grafana/**'
tags:
- "*"
workflow_dispatch:
jobs:
@@ -40,7 +42,7 @@ jobs:
source .env
docker-compose stop || true
docker-compose rm || true
docker-compose up -d db && sleep 15
docker-compose up -d db && sleep 30
docker-compose ps -a
echo "Test PostgSail Grafana Unit Test"
docker-compose up -d app && sleep 5

8
.gitignore vendored
View File

@@ -1,2 +1,10 @@
.DS_Store
.env
initdb/*.csv
initdb/*.no
initdb/*.jwk
tests/node_modules/
tests/output/
assets/*
.pnpm-store/
db-data/

22
.schemalintrc.js Normal file
View File

@@ -0,0 +1,22 @@
module.exports = {
connection: {
host: process.env.PGHOST,
user: process.env.PGUSER,
password: process.env.PGPASSWORD,
database: process.env.PGDATABASE,
charset: "utf8",
},
rules: {
"name-casing": ["error", "snake"],
"prefer-jsonb-to-json": ["error"],
"prefer-text-to-varchar": ["error"],
"prefer-timestamptz-to-timestamp": ["error"],
"prefer-identity-to-serial": ["error"],
//"name-inflection": ["error", "singular"],
},
schemas: [{ name: "public" }, { name: "api" },{ name: "auth" }],
ignores: [],
};

View File

@@ -2,34 +2,33 @@
The Entity-Relationship Diagram (ERD) provides a graphical representation of database tables, columns, and inter-relationships. ERD can give sufficient information for the database administrator to follow when developing and maintaining the database.
## A global overview
![API Schema](https://raw.githubusercontent.com/xbgmsharp/postgsail/main/ERD/postgsail.pgerd.png "API Schema")
Auto generated Mermaid diagram using [mermerd](https://github.com/KarnerTh/mermerd) and [MermaidJs](https://github.com/mermaid-js/mermaid).
[PostgSail SQL Schema](https://github.com/xbgmsharp/postgsail/tree/main/ERD/postgsail.md "PostgSail SQL Schema")
## Further
There is 3 main schemas:
- API Schema ERD
- tables
- metrics
- logbook
- ...
- functions
- ...
![API Schema](https://raw.githubusercontent.com/xbgmsharp/postgsail/main/ERD/signalk%20-%20api.png)
- API Schema:
- tables
- metrics
- logbook
- ...
- functions
- ...
- Auth Schema ERD
- Auth Schema:
- tables
- accounts
- vessels
- ...
- functions
- ...
![Auth Schema](https://raw.githubusercontent.com/xbgmsharp/postgsail/main/ERD/signalk%20-%20auth.png "Auth Schema")
- Public Schema ERD
- Public Schema:
- tables
- app_settings
- tpl_messages
- ...
- functions
- ...
![Public Schema](https://raw.githubusercontent.com/xbgmsharp/postgsail/main/ERD/signalk%20-%20public.png "Public Schema")

35
ERD/mermerdConfig.yaml Normal file
View File

@@ -0,0 +1,35 @@
# Connection properties
connectionString: ${PGSAIL_DB_URI}
# Define what schemas should be used
#useAllSchemas: true
# or
schema:
- "public"
- "api"
- "auth"
# Define what tables should be used
useAllTables: true
# or
#selectedTables:
# - city
# - customer
# Additional flags
showAllConstraints: true
encloseWithMermaidBackticks: true
outputFileName: "postgsail.md"
debug: true
omitConstraintLabels: true
omitAttributeKeys: true
showDescriptions:
- enumValues
- columnComments
- notNull
showSchemaPrefix: true
schemaPrefixSeparator: "_"
# Names must match the pattern <schema><schema_prefix><table>
#relationshipLabels:
# - "public_table public_another-table : label"

246
ERD/postgsail.md Normal file
View File

@@ -0,0 +1,246 @@
```mermaid
erDiagram
api_logbook {
text _from
double_precision _from_lat
double_precision _from_lng
integer _from_moorage_id "Link api.moorages with api.logbook via FOREIGN KEY and REFERENCES"
timestamp_with_time_zone _from_time "{NOT_NULL}"
text _to
double_precision _to_lat
double_precision _to_lng
integer _to_moorage_id "Link api.moorages with api.logbook via FOREIGN KEY and REFERENCES"
timestamp_with_time_zone _to_time
boolean active
double_precision avg_speed
numeric distance "in NM"
interval duration "Best to use standard ISO 8601"
jsonb extra "computed signalk metrics of interest, runTime, currentLevel, etc"
integer id "{NOT_NULL}"
double_precision max_speed
double_precision max_wind_speed
text name
text notes
geography track_geog "postgis geography type default SRID 4326 Unit: degres"
jsonb track_geojson "store generated geojson with track metrics data using with LineString and Point features, we can not depend api.metrics table"
geometry track_geom "postgis geometry type EPSG:4326 Unit: degres"
text vessel_id "{NOT_NULL}"
}
api_metadata {
boolean active "trigger monitor online/offline"
boolean active
double_precision beam
text client_id
timestamp_with_time_zone created_at "{NOT_NULL}"
double_precision height
integer id "{NOT_NULL}"
double_precision length
numeric mmsi
text name
text plugin_version "{NOT_NULL}"
numeric ship_type
text signalk_version "{NOT_NULL}"
timestamp_with_time_zone time "{NOT_NULL}"
timestamp_with_time_zone updated_at "{NOT_NULL}"
text vessel_id "Link auth.vessels with api.metadata via FOREIGN KEY and REFERENCES {NOT_NULL}"
text vessel_id "{NOT_NULL}"
}
api_metrics {
double_precision anglespeedapparent
text client_id
double_precision courseovergroundtrue
double_precision latitude "With CONSTRAINT but allow NULL value to be ignored silently by trigger"
double_precision longitude "With CONSTRAINT but allow NULL value to be ignored silently by trigger"
jsonb metrics
double_precision speedoverground
status status "<sailing,motoring,moored,anchored>"
timestamp_with_time_zone time "{NOT_NULL}"
text vessel_id "{NOT_NULL}"
double_precision windspeedapparent
}
api_moorages {
text country
geography geog "postgis geography type default SRID 4326 Unit: degres"
boolean home_flag
integer id "{NOT_NULL}"
double_precision latitude
double_precision longitude
text name
jsonb nominatim
text notes
jsonb overpass
integer reference_count
integer stay_code "Link api.stays_at with api.moorages via FOREIGN KEY and REFERENCES"
interval stay_duration "Best to use standard ISO 8601"
text vessel_id "{NOT_NULL}"
}
api_stays {
boolean active
timestamp_with_time_zone arrived "{NOT_NULL}"
timestamp_with_time_zone departed
interval duration "Best to use standard ISO 8601"
geography geog "postgis geography type default SRID 4326 Unit: degres"
integer id "{NOT_NULL}"
double_precision latitude
double_precision longitude
integer moorage_id "Link api.moorages with api.stays via FOREIGN KEY and REFERENCES"
text name
text notes
integer stay_code "Link api.stays_at with api.stays via FOREIGN KEY and REFERENCES"
text vessel_id "{NOT_NULL}"
}
api_stays_at {
text description "{NOT_NULL}"
integer stay_code "{NOT_NULL}"
}
auth_accounts {
timestamp_with_time_zone connected_at "{NOT_NULL}"
timestamp_with_time_zone created_at "{NOT_NULL}"
citext email "{NOT_NULL}"
text first "User first name with CONSTRAINT CHECK {NOT_NULL}"
text last "User last name with CONSTRAINT CHECK {NOT_NULL}"
text pass "{NOT_NULL}"
jsonb preferences
integer public_id "{NOT_NULL}"
name role "{NOT_NULL}"
timestamp_with_time_zone updated_at "{NOT_NULL}"
text user_id "{NOT_NULL}"
}
auth_otp {
text otp_pass "{NOT_NULL}"
timestamp_with_time_zone otp_timestamp
smallint otp_tries "{NOT_NULL}"
citext user_email "{NOT_NULL}"
}
auth_vessels {
timestamp_with_time_zone created_at "{NOT_NULL}"
numeric mmsi
text name "{NOT_NULL}"
citext owner_email "{NOT_NULL}"
name role "{NOT_NULL}"
timestamp_with_time_zone updated_at "{NOT_NULL}"
text vessel_id "{NOT_NULL}"
}
public_aistypes {
text description
numeric id
}
public_app_settings {
text name "application settings name key {NOT_NULL}"
text value "application settings value {NOT_NULL}"
}
public_badges {
text description
text name
}
public_email_templates {
text email_content
text email_subject
text name
text pushover_message
text pushover_title
}
public_geocoders {
text name
text reverse_url
text url
}
public_iso3166 {
text alpha_2
text alpha_3
text country
integer id
}
public_mid {
text country
integer country_id
numeric id
}
public_ne_10m_geography_marine_polys {
text changed
text featurecla
geometry geom
integer gid "{NOT_NULL}"
text label
double_precision max_label
double_precision min_label
text name
text name_ar
text name_bn
text name_de
text name_el
text name_en
text name_es
text name_fa
text name_fr
text name_he
text name_hi
text name_hu
text name_id
text name_it
text name_ja
text name_ko
text name_nl
text name_pl
text name_pt
text name_ru
text name_sv
text name_tr
text name_uk
text name_ur
text name_vi
text name_zh
text name_zht
text namealt
bigint ne_id
text note
smallint scalerank
text wikidataid
}
public_process_queue {
text channel "{NOT_NULL}"
integer id "{NOT_NULL}"
text payload "{NOT_NULL}"
timestamp_with_time_zone processed
text ref_id "either user_id or vessel_id {NOT_NULL}"
timestamp_with_time_zone stored "{NOT_NULL}"
}
public_spatial_ref_sys {
character_varying auth_name
integer auth_srid
character_varying proj4text
integer srid "{NOT_NULL}"
character_varying srtext
}
api_logbook }o--|| api_metadata : ""
api_logbook }o--|| api_moorages : ""
api_logbook }o--|| api_moorages : ""
api_metadata }o--|| auth_vessels : ""
api_metrics }o--|| api_metadata : ""
api_moorages }o--|| api_metadata : ""
api_stays }o--|| api_metadata : ""
api_moorages }o--|| api_stays_at : ""
api_stays }o--|| api_moorages : ""
api_stays }o--|| api_stays_at : ""
auth_otp |o--|| auth_accounts : ""
auth_vessels |o--|| auth_accounts : ""
```

Binary file not shown.

Before

Width:  |  Height:  |  Size: 222 KiB

After

Width:  |  Height:  |  Size: 222 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 194 KiB

After

Width:  |  Height:  |  Size: 195 KiB

View File

@@ -2,21 +2,40 @@
Effortless cloud based solution for storing and sharing your SignalK data. Allow you to effortlessly log your sails and monitor your boat with historical data.
[![release](https://img.shields.io/github/release/xbgmsharp/postgsail?include_prereleases=&sort=semver&color=blue)](https://github.com/xbgmsharp/postgsail/releases/latest)
[![License](https://img.shields.io/github/license/xbgmsharp/postgsail)](#license)
[![issues - postgsail](https://img.shields.io/github/issues/xbgmsharp/postgsail)](https://github.com/xbgmsharp/postgsail/issues)
[![Test services db, api](https://github.com/xbgmsharp/postgsail/actions/workflows/db-test.yml/badge.svg)](https://github.com/xbgmsharp/postgsail/actions/workflows/db-test.yml)
[![Test services db, api, web](https://github.com/xbgmsharp/postgsail/actions/workflows/frontend-test.yml/badge.svg)](https://github.com/xbgmsharp/postgsail/actions/workflows/frontend-test.yml)
[![Test services db, grafana](https://github.com/xbgmsharp/postgsail/actions/workflows/grafana-test.yml/badge.svg)](https://github.com/xbgmsharp/postgsail/actions/workflows/grafana-test.yml)
signalk-postgsail:
[![GitHub Release](https://img.shields.io/github/release/xbgmsharp/signalk-postgsail.svg)](https://github.com/xbgmsharp/signalk-postgsail/releases/latest)
postgsail-frontend:
[![GitHub Release](https://img.shields.io/github/release/xbgmsharp/vuestic-postgsail.svg)](https://github.com/xbgmsharp/vuestic-postgsail/releases/latest)
postgsail-telegram-bot:
[![GitHub Release](https://img.shields.io/github/release/xbgmsharp/postgsail-telegram-bot.svg)](https://github.com/xbgmsharp/postgsail-telegram-bot/releases/latest)
## Features
- Automatically log your voyages without manually starting or stopping a trip.
- Automatically capture the details of your voyages (boat speed, heading, wind speed, etc).
- Timelapse video your trips!
- Timelapse video your trips, with or without time control.
- Add custom notes to your logs.
- Export to CSV or GPX and download your logs.
- Export to CSV, GPX, GeoJSON, KML and download your logs.
- Aggregate your trip statistics: Longest voyage, time spent at anchorages, home ports etc.
- See your moorages on a global map, with incoming and outgoing voyages from each trip.
- Monitor your boat (position, depth, wind, temperature, battery charge status, etc.) remotely.
- History: view trends.
- Alert monitoring: get notification on low voltage or low fuel remotely.
- Notification via email or PushOver, Telegram
- Offline mode
- Low Bandwidth mode
- Notification via email or PushOver, Telegram.
- Offline mode.
- Low Bandwidth mode.
- Awesome statistics and graphs.
- Anything missing? just ask!
## Context
@@ -44,18 +63,20 @@ Hosted and fullymanaged options for PostgSail, designed for all your deployme
## Using PostgSail
### full-featured development environment
The Visual Studio Code Remote - Containers extension lets you use a Docker container as a full-featured development environment.
A full-featured development environment.
#### With codesandbox
- https://codesandbox.io/p/github/xbgmsharp/postgsail/main
#### With CodeSandbox
- Develop on [![CodeSandbox Ready-to-Code](https://img.shields.io/badge/CodeSandbox-Ready--to--Code-blue?logo=codesandbox)](https://codesandbox.io/p/github/xbgmsharp/postgsail/main)
- or via [direct link](https://codesandbox.io/p/github/xbgmsharp/postgsail/main)
#### With DevPod
- https://devpod.sh/open#https://github.com/xbgmsharp/postgsail/&workspace=postgsail&provider=docker&ide=openvscode
- [![Open in DevPod!](https://devpod.sh/assets/open-in-devpod.svg)](https://devpod.sh/open#https://github.com/xbgmsharp/postgsail/&workspace=postgsail&provider=docker&ide=openvscode)
- or via [direct link](https://devpod.sh/open#https://github.com/xbgmsharp/postgsail&workspace=postgsail&provider=docker&ide=openvscode)
#### With Docker Dev Environments
- https://open.docker.com/dashboard/dev-envs?url=https://github.com/xbgmsharp/postgsail/
Open in Docker Dev Environments Open in Docker Dev Environments
- [Open in Docker dev-envs!](https://open.docker.com/dashboard/dev-envs?url=https://github.com/xbgmsharp/postgsail/)
### pre-deploy configuration
@@ -75,12 +96,12 @@ Notice, that `PGRST_JWT_SECRET` must be at least 32 characters long.
### Deploy
By default there is no network set and the postgresql data are store in a docker volume.
You can update the default settings by editing `docker-compose.yml` to your need.
By default there is no network set and all data are store in a docker volume.
You can update the default settings by editing `docker-compose.yml` and `docker-compose.dev.yml` to your need.
First let's initialize the database.
#### Initialize database
#### Step 1. Initialize database
First let's import the SQL schema, execute:
@@ -88,7 +109,7 @@ First let's import the SQL schema, execute:
$ docker-compose up db
```
#### Start backend (db, api)
#### Step 2. Start backend (db, api)
Then launch the full stack (db, api) backend, execute:
@@ -123,10 +144,11 @@ Next, to ingest data from signalk, you need to install [signalk-postgsail](https
Also, if you like, you can import saillogger data using the postgsail helpers, [postgsail-helpers](https://github.com/xbgmsharp/postgsail-helpers).
You might want to import your influxdb1 data as well, [outflux](https://github.com/timescale/outflux).
Any taker on influxdb2 to PostgSail? It is definitely possible.
For InfluxDB 2.x and 3.x. You will need to enable the 1.x APIs to use them. Consult the InfluxDB documentation for more details.
Last, if you like, you can import the sample data from Signalk NMEA Plaka by running the tests.
If everything goes well all tests pass successfully and you should receive a few notifications by email or PushOver.
If everything goes well all tests pass successfully and you should receive a few notifications by email or PushOver or Telegram.
[End-to-End (E2E) Testing.](https://github.com/xbgmsharp/postgsail/blob/main/tests/)
```
$ docker-compose up tests
@@ -136,6 +158,8 @@ $ docker-compose up tests
The OpenAPI description output depends on the permissions of the role that is contained in the JWT role claim.
Other applications can also use the [PostgSAIL API](https://petstore.swagger.io/?url=https://raw.githubusercontent.com/xbgmsharp/postgsail/main/openapi.json).
API anonymous:
```
@@ -156,7 +180,7 @@ $ curl http://localhost:3000/ -H 'Authorization: Bearer my_token_from_register_v
#### API main workflow
Check the [unit test sample](https://github.com/xbgmsharp/postgsail/blob/main/tests/index.js).
Check the [End-to-End (E2E) test sample](https://github.com/xbgmsharp/postgsail/blob/main/tests/).
### Docker dependencies
@@ -185,10 +209,6 @@ Out of the box iot platform using docker with the following software:
- [PostGIS, a spatial database extender for PostgreSQL object-relational database.](https://postgis.net/)
- [Grafana, open observability platform | Grafana Labs](https://grafana.com)
### Releases & updates
PostgSail Release Notes & Future Plans: see planned and in-progress updates and detailed information about current and past releases. [PostgSail project](https://github.com/xbgmsharp?tab=projects)
### Support
To get support, please create new [issue](https://github.com/xbgmsharp/postgsail/issues).
@@ -202,4 +222,4 @@ Feel free to contribute.
### License
This script is free software, Apache License Version 2.0.
This is a free software, Apache License Version 2.0.

View File

@@ -90,6 +90,7 @@ services:
context: https://github.com/xbgmsharp/vuestic-postgsail.git#live
dockerfile: Dockerfile_dev
container_name: web_dev
hostname: web_dev
restart: unless-stopped
volumes:
- ./frontend:/app
@@ -97,8 +98,9 @@ services:
- "api:postgrest"
ports:
- 8080:8080
env_file: .env
environment:
- VITE_PGSAIL_URL=${PGSAIL_API_URL}
- VITE_PGSAIL_URL=http://api:3000
- VITE_APP_INCLUDE_DEMOS=false
- VITE_APP_BUILD_VERSION=true
- VITE_APP_TITLE=${VITE_APP_TITLE}
@@ -109,5 +111,25 @@ services:
options:
max-size: 10m
web_tests:
image: cypress/included
container_name: web_tests
restart: unless-stopped
volumes:
- ./frontend/e2e:/e2e
links:
- "api:postgrest"
- "web_dev:frontend"
env_file: .env
environment:
- CYPRESS_BASE_URL=http://web_dev:8080
depends_on:
- db
- api
- web_dev
logging:
options:
max-size: 10m
volumes:
data: {}

View File

@@ -45,6 +45,7 @@ services:
PGRST_DB_ANON_ROLE: api_anonymous
PGRST_OPENAPI_SERVER_PROXY_URI: http://127.0.0.1:3000
PGRST_DB_PRE_REQUEST: public.check_jwt
PGRST_DB_POOL: 20
PGRST_DB_URI: ${PGRST_DB_URI}
PGRST_JWT_SECRET: ${PGRST_JWT_SECRET}
depends_on:

View File

@@ -20,8 +20,21 @@
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": 6,
"links": [],
"id": 1,
"links": [
{
"asDropdown": false,
"icon": "external link",
"includeVars": true,
"keepTime": false,
"tags": [],
"targetBlank": true,
"title": "New link",
"tooltip": "",
"type": "dashboards",
"url": ""
}
],
"liveNow": false,
"panels": [
{
@@ -83,7 +96,7 @@
"showThresholdLabels": false,
"showThresholdMarkers": true
},
"pluginVersion": "9.5.1",
"pluginVersion": "10.1.0",
"targets": [
{
"datasource": {
@@ -220,7 +233,7 @@
},
"textMode": "auto"
},
"pluginVersion": "9.5.1",
"pluginVersion": "10.1.0",
"targets": [
{
"datasource": {
@@ -346,7 +359,7 @@
},
"textMode": "auto"
},
"pluginVersion": "9.5.1",
"pluginVersion": "10.1.0",
"targets": [
{
"datasource": {
@@ -481,7 +494,7 @@
},
"textMode": "auto"
},
"pluginVersion": "9.5.1",
"pluginVersion": "10.1.0",
"targets": [
{
"datasource": {
@@ -591,7 +604,7 @@
},
"textMode": "auto"
},
"pluginVersion": "9.5.1",
"pluginVersion": "10.1.0",
"targets": [
{
"datasource": {
@@ -719,7 +732,7 @@
},
"textMode": "auto"
},
"pluginVersion": "9.5.1",
"pluginVersion": "10.1.0",
"targets": [
{
"datasource": {
@@ -766,50 +779,108 @@
"type": "stat"
},
{
"aliasColors": {
"electrical.batteries.256.current.mean": "blue"
},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": {
"type": "postgres",
"uid": "PCC52D03280B7034C"
},
"fill": 1,
"fillGradient": 0,
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 10,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "line+area"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "transparent",
"value": null
},
{
"color": "red",
"value": -1
},
{
"color": "red",
"value": 1
}
]
},
"unit": "amp"
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "electrical.batteries.256.current.mean"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "blue",
"mode": "fixed"
}
}
]
}
]
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 5
},
"hiddenSeries": false,
"id": 47,
"legend": {
"avg": true,
"current": false,
"max": true,
"min": true,
"show": true,
"total": false,
"values": true
},
"lines": true,
"linewidth": 1,
"nullPointMode": "null",
"options": {
"alertThreshold": true
"legend": {
"calcs": [
"mean",
"max",
"min"
],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"percentage": false,
"pluginVersion": "9.5.1",
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"pluginVersion": "10.1.0",
"targets": [
{
"datasource": {
@@ -835,7 +906,8 @@
"measurement": "electrical.batteries.256.current",
"orderByTime": "ASC",
"policy": "default",
"rawSql": "",
"rawQuery": true,
"rawSql": "SET vessel.id = '${__user.login}';\nSELECT m.time, cast(m.metrics->'electrical.batteries.House.current' as NUMERIC) as current FROM api.metrics m WHERE $__timeFilter(time) AND m.vessel_id = '${boat}';\n",
"refId": "A",
"resultFormat": "time_series",
"select": [
@@ -872,56 +944,379 @@
"tags": []
}
],
"thresholds": [
{
"$$hashKey": "object:8288",
"colorMode": "critical",
"fill": true,
"line": true,
"op": "gt",
"value": -1,
"yaxis": "left"
},
{
"$$hashKey": "object:8294",
"colorMode": "ok",
"fill": true,
"line": true,
"op": "gt",
"value": 1,
"yaxis": "left"
}
],
"timeRegions": [],
"title": "House Amps",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
"type": "timeseries"
},
{
"datasource": {
"type": "postgres",
"uid": "PCC52D03280B7034C"
},
"type": "graph",
"xaxis": {
"mode": "time",
"show": true,
"values": []
},
"yaxes": [
{
"$$hashKey": "object:8148",
"format": "amp",
"logBase": 1,
"show": true
"description": "",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 10,
"x": 12,
"y": 5
},
"id": 48,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"$$hashKey": "object:8149",
"format": "short",
"logBase": 1,
"show": true
"datasource": {
"type": "postgres",
"uid": "PCC52D03280B7034C"
},
"editorMode": "code",
"format": "table",
"rawQuery": true,
"rawSql": "SET vessel.id = '${__user.login}';\nSELECT m.time, cast(m.metrics->'electrical.batteries.House.capacity.stateOfCharge' as NUMERIC) * 100 as stateOfCharge FROM api.metrics m WHERE $__timeFilter(time) AND m.vessel_id = '${boat}';\n",
"refId": "A",
"sql": {
"columns": [
{
"parameters": [],
"type": "function"
}
],
"groupBy": [
{
"property": {
"type": "string"
},
"type": "groupBy"
}
],
"limit": 50
}
}
],
"yaxis": {
"align": false
}
"title": "System - Battery SOC (State of Charge)",
"type": "timeseries"
},
{
"datasource": {
"type": "postgres",
"uid": "PCC52D03280B7034C"
},
"description": "",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "Volts",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 10,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "volt"
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "current"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "blue",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byName",
"options": "voltage"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "yellow",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byName",
"options": "current"
},
"properties": [
{
"id": "unit",
"value": "amp"
},
{
"id": "custom.axisLabel",
"value": "Amps"
}
]
}
]
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 13
},
"id": 37,
"options": {
"legend": {
"calcs": [
"mean",
"max",
"min"
],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"pluginVersion": "10.1.0",
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "PCC52D03280B7034C"
},
"editorMode": "code",
"format": "table",
"groupBy": [
{
"params": [
"$__interval"
],
"type": "time"
},
{
"params": [
"null"
],
"type": "fill"
}
],
"measurement": "electrical.batteries.256.voltage",
"orderByTime": "ASC",
"policy": "default",
"rawQuery": true,
"rawSql": "SET vessel.id = '${__user.login}';\nSELECT m.time, cast(m.metrics->'electrical.batteries.House.voltage' as NUMERIC) as voltage FROM api.metrics m WHERE $__timeFilter(time) AND m.vessel_id = '${boat}';\n",
"refId": "A",
"resultFormat": "time_series",
"select": [
[
{
"params": [
"value"
],
"type": "field"
},
{
"params": [],
"type": "mean"
}
]
],
"sql": {
"columns": [
{
"parameters": [],
"type": "function"
}
],
"groupBy": [
{
"property": {
"type": "string"
},
"type": "groupBy"
}
],
"limit": 50
},
"tags": []
},
{
"datasource": {
"type": "postgres",
"uid": "PCC52D03280B7034C"
},
"editorMode": "code",
"format": "table",
"groupBy": [
{
"params": [
"$__interval"
],
"type": "time"
},
{
"params": [
"null"
],
"type": "fill"
}
],
"hide": false,
"measurement": "electrical.batteries.256.current",
"orderByTime": "ASC",
"policy": "default",
"query": "SELECT mean(\"value\") FROM \"electrical.batteries.256.current\" WHERE $timeFilter GROUP BY time($__interval) fill(null)",
"rawQuery": true,
"rawSql": "SET vessel.id = '${__user.login}';\nSELECT m.time, cast(m.metrics->'electrical.batteries.House.current' as NUMERIC) as current FROM api.metrics m WHERE $__timeFilter(time) AND m.vessel_id = '${boat}';\n",
"refId": "B",
"resultFormat": "time_series",
"select": [
[
{
"params": [
"value"
],
"type": "field"
},
{
"params": [],
"type": "mean"
}
]
],
"sql": {
"columns": [
{
"parameters": [],
"type": "function"
}
],
"groupBy": [
{
"property": {
"type": "string"
},
"type": "groupBy"
}
],
"limit": 50
},
"tags": []
}
],
"title": "Battery Voltage and Current",
"type": "timeseries"
},
{
"aliasColors": {
@@ -938,9 +1333,9 @@
"fillGradient": 0,
"gridPos": {
"h": 8,
"w": 12,
"w": 10,
"x": 12,
"y": 5
"y": 13
},
"hiddenSeries": false,
"id": 45,
@@ -960,7 +1355,7 @@
"alertThreshold": true
},
"percentage": false,
"pluginVersion": "9.5.1",
"pluginVersion": "10.1.0",
"pointradius": 2,
"points": false,
"renderer": "flot",
@@ -1042,180 +1437,6 @@
"align": false
}
},
{
"aliasColors": {
"electrical.batteries.256.current.mean": "blue",
"electrical.batteries.256.voltage.mean": "yellow"
},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": {
"type": "postgres",
"uid": "PCC52D03280B7034C"
},
"description": "",
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 13
},
"hiddenSeries": false,
"id": 37,
"legend": {
"alignAsTable": false,
"avg": true,
"current": false,
"max": true,
"min": true,
"rightSide": false,
"show": true,
"total": false,
"values": true
},
"lines": true,
"linewidth": 1,
"nullPointMode": "null",
"options": {
"alertThreshold": true
},
"percentage": false,
"pluginVersion": "9.5.1",
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [
{
"$$hashKey": "object:5017",
"alias": "electrical.batteries.256.current.mean",
"yaxis": 2
}
],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "PCC52D03280B7034C"
},
"groupBy": [
{
"params": [
"$__interval"
],
"type": "time"
},
{
"params": [
"null"
],
"type": "fill"
}
],
"measurement": "electrical.batteries.256.voltage",
"orderByTime": "ASC",
"policy": "default",
"refId": "A",
"resultFormat": "time_series",
"select": [
[
{
"params": [
"value"
],
"type": "field"
},
{
"params": [],
"type": "mean"
}
]
],
"tags": []
},
{
"datasource": {
"type": "postgres",
"uid": "PCC52D03280B7034C"
},
"groupBy": [
{
"params": [
"$__interval"
],
"type": "time"
},
{
"params": [
"null"
],
"type": "fill"
}
],
"hide": false,
"measurement": "electrical.batteries.256.current",
"orderByTime": "ASC",
"policy": "default",
"query": "SELECT mean(\"value\") FROM \"electrical.batteries.256.current\" WHERE $timeFilter GROUP BY time($__interval) fill(null)",
"rawQuery": true,
"refId": "B",
"resultFormat": "time_series",
"select": [
[
{
"params": [
"value"
],
"type": "field"
},
{
"params": [],
"type": "mean"
}
]
],
"tags": []
}
],
"thresholds": [],
"timeRegions": [],
"title": "House Bank Voltage vs Current",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"mode": "time",
"show": true,
"values": []
},
"yaxes": [
{
"$$hashKey": "object:4372",
"format": "volt",
"label": "Volts",
"logBase": 1,
"show": true
},
{
"$$hashKey": "object:4373",
"format": "amp",
"label": "Amps",
"logBase": 1,
"show": true
}
],
"yaxis": {
"align": false
}
},
{
"aliasColors": {
"From grid": "#1f78c1",
@@ -1232,9 +1453,9 @@
"fillGradient": 0,
"gridPos": {
"h": 8,
"w": 12,
"w": 10,
"x": 12,
"y": 13
"y": 21
},
"hiddenSeries": false,
"id": 10,
@@ -1258,7 +1479,7 @@
},
"paceLength": 10,
"percentage": false,
"pluginVersion": "9.5.1",
"pluginVersion": "10.1.0",
"pointradius": 2,
"points": false,
"renderer": "flot",
@@ -1416,6 +1637,25 @@
"skipUrlSync": false,
"sort": 0,
"type": "query"
},
{
"datasource": {
"type": "postgres",
"uid": "PCC52D03280B7034C"
},
"definition": "SET vessel.id = '${__user.login}';\nSELECT rtrim(key, 'voltage') AS __text ,key AS __value FROM api.monitoring_view2 where key ILIKE 'electrical.batteries%voltage';",
"hide": 0,
"includeAll": false,
"label": "Batteries",
"multi": false,
"name": "batteries",
"options": [],
"query": "SET vessel.id = '${__user.login}';\nSELECT rtrim(key, 'voltage') AS __text ,key AS __value FROM api.monitoring_view2 where key ILIKE 'electrical.batteries%voltage';",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 0,
"type": "query"
}
]
},
@@ -1447,9 +1687,9 @@
"30d"
]
},
"timezone": "",
"timezone": "utc",
"title": "Electrical System",
"uid": "rk0FTiIMk",
"version": 1,
"version": 11,
"weekStart": ""
}

View File

@@ -25,7 +25,7 @@
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": 2,
"id": 3,
"links": [
{
"asDropdown": false,
@@ -92,7 +92,7 @@
"text": {},
"textMode": "auto"
},
"pluginVersion": "9.4.3",
"pluginVersion": "10.1.0",
"targets": [
{
"datasource": {
@@ -198,7 +198,7 @@
"text": {},
"textMode": "auto"
},
"pluginVersion": "9.4.3",
"pluginVersion": "10.1.0",
"targets": [
{
"datasource": {
@@ -279,6 +279,7 @@
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
@@ -439,6 +440,7 @@
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
@@ -573,6 +575,7 @@
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
@@ -638,7 +641,7 @@
"group": [],
"metricColumn": "none",
"rawQuery": true,
"rawSql": "SET vessel.id = '${__user.login}';\nwith config as (select set_config('vessel.id', '${boat}', false) ) select * from api.monitoring_view",
"rawSql": "SET vessel.id = '${__user.login}';\nselect * from api.monitoring_humidity;\n",
"refId": "A",
"select": [
[
@@ -679,11 +682,11 @@
]
}
],
"title": "Title",
"title": "environment.%.humidity",
"type": "timeseries"
}
],
"refresh": "",
"refresh": "5m",
"revision": 1,
"schemaVersion": 38,
"style": "dark",

File diff suppressed because it is too large Load Diff

View File

@@ -1936,7 +1936,7 @@
"yBucketBound": "auto"
}
],
"refresh": "1m",
"refresh": "5m",
"schemaVersion": 37,
"style": "dark",
"tags": [],

View File

@@ -1,134 +1,297 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "grafana",
"uid": "-- Grafana --"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"target": {
"limit": 100,
"matchAny": false,
"tags": [],
"type": "dashboard"
},
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "grafana",
"uid": "-- Grafana --"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"target": {
"limit": 100,
"matchAny": false,
"tags": [],
"type": "dashboard"
}
]
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"links": [],
"liveNow": false,
"panels": [
{
"datasource": {
"type": "postgres",
"uid": "OIttR1sVk"
},
"gridPos": {
"h": 3,
"w": 24,
"x": 0,
"y": 0
},
"id": 1,
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "OIttR1sVk"
},
"refId": "A"
}
],
"type": "welcome"
},
{
"datasource": {
"type": "postgres",
"uid": "OIttR1sVk"
},
"gridPos": {
"h": 12,
"w": 24,
"x": 0,
"y": 3
},
"id": 3,
"links": [],
"options": {
"folderId": 0,
"maxItems": 30,
"query": "",
"showHeadings": true,
"showRecentlyViewed": true,
"showSearch": false,
"showStarred": true,
"tags": []
},
"pluginVersion": "9.4.3",
"tags": [],
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "OIttR1sVk"
},
"refId": "A"
}
],
"title": "Dashboards",
"type": "dashlist"
"type": "dashboard"
}
]
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": 5,
"links": [
{
"asDropdown": false,
"icon": "external link",
"includeVars": true,
"keepTime": false,
"tags": [],
"targetBlank": true,
"title": "New link",
"tooltip": "",
"type": "dashboards",
"url": ""
}
],
"liveNow": false,
"panels": [
{
"datasource": {
"type": "postgres",
"uid": "OIttR1sVk"
},
"gridPos": {
"h": 13,
"w": 10,
"x": 0,
"y": 0
},
"id": 3,
"links": [],
"options": {
"folderId": 0,
"includeVars": false,
"keepTime": false,
"maxItems": 30,
"query": "",
"showHeadings": true,
"showRecentlyViewed": true,
"showSearch": false,
"showStarred": true,
"tags": []
},
"pluginVersion": "10.1.4",
"tags": [],
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "OIttR1sVk"
},
"refId": "A"
}
],
"title": "PostgSail Dashboards",
"type": "dashlist"
},
{
"datasource": {
"type": "postgres",
"uid": "PCC52D03280B7034C"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"custom": {
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 13,
"w": 12,
"x": 10,
"y": 0
},
"id": 5,
"maxDataPoints": 500,
"options": {
"basemap": {
"config": {},
"name": "Layer 0",
"type": "default"
},
"controls": {
"mouseWheelZoom": true,
"showAttribution": true,
"showDebug": false,
"showMeasure": false,
"showScale": false,
"showZoom": true
},
"layers": [
{
"config": {
"showLegend": true,
"style": {
"color": {
"fixed": "dark-green"
},
"opacity": 0.4,
"rotation": {
"fixed": 0,
"max": 360,
"min": -360,
"mode": "mod"
},
"size": {
"fixed": 5,
"max": 15,
"min": 2
},
"symbol": {
"fixed": "img/icons/marker/circle.svg",
"mode": "fixed"
},
"textConfig": {
"fontSize": 12,
"offsetX": 0,
"offsetY": 0,
"textAlign": "center",
"textBaseline": "middle"
}
}
},
"filterData": {
"id": "byRefId",
"options": "A"
},
"location": {
"latitude": "value",
"longitude": "value",
"mode": "auto"
},
"name": "Boat",
"tooltip": true,
"type": "markers"
}
],
"tooltip": {
"mode": "details"
},
"view": {
"allLayers": true,
"id": "fit",
"lat": 0,
"lon": 0,
"zoom": 5
}
},
"pluginVersion": "10.1.4",
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "PCC52D03280B7034C"
},
"editorMode": "code",
"format": "table",
"rawQuery": true,
"rawSql": "SELECT latitude, longitude FROM api.metrics WHERE vessel_id = '${boat}' ORDER BY time ASC LIMIT 1;",
"refId": "A",
"sql": {
"columns": [
{
"parameters": [],
"type": "function"
}
],
"groupBy": [
{
"property": {
"type": "string"
},
"type": "groupBy"
}
],
"limit": 50
}
}
],
"title": "Location",
"type": "geomap"
}
],
"refresh": "",
"revision": 1,
"schemaVersion": 38,
"style": "dark",
"tags": [],
"templating": {
"list": [
{
"datasource": {
"type": "postgres",
"uid": "PCC52D03280B7034C"
},
"definition": "SET \"user.email\" = '${__user.email}';\nSET vessel.id = '${__user.login}';\nSELECT\n v.name AS __text,\n m.vessel_id AS __value\n FROM auth.vessels v\n JOIN api.metadata m ON v.owner_email = '${__user.email}' and m.vessel_id = v.vessel_id;",
"description": "Vessel Name",
"hide": 0,
"includeAll": false,
"label": "Boat",
"multi": false,
"name": "boat",
"options": [],
"query": "SET \"user.email\" = '${__user.email}';\nSET vessel.id = '${__user.login}';\nSELECT\n v.name AS __text,\n m.vessel_id AS __value\n FROM auth.vessels v\n JOIN api.metadata m ON v.owner_email = '${__user.email}' and m.vessel_id = v.vessel_id;",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 0,
"type": "query"
}
]
},
"time": {
"from": "now-90d",
"to": "now"
},
"timepicker": {
"hidden": true,
"refresh_intervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
],
"refresh": "",
"revision": 1,
"schemaVersion": 38,
"style": "dark",
"tags": [],
"templating": {
"list": []
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {
"hidden": true,
"refresh_intervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
],
"time_options": [
"5m",
"15m",
"1h",
"6h",
"12h",
"24h",
"2d",
"7d",
"30d"
],
"type": "timepicker"
},
"timezone": "browser",
"title": "Home",
"version": 0,
"weekStart": ""
}
"time_options": [
"5m",
"15m",
"1h",
"6h",
"12h",
"24h",
"2d",
"7d",
"30d"
],
"type": "timepicker"
},
"timezone": "browser",
"title": "Home",
"uid": "d81aa15b",
"version": 1,
"weekStart": ""
}

View File

@@ -7,6 +7,7 @@ auto_assign_org_role = Editor
enabled = true
header_name = X-WEBAUTH-USER
header_property = email
headers = Login:X-WEBAUTH-LOGIN
auto_sign_up = true
enable_login_token = true
login_maximum_inactive_lifetime_duration = 12h
@@ -14,3 +15,7 @@ login_maximum_lifetime_duration = 1d
[dashboards]
default_home_dashboard_path = /etc/grafana/dashboards/home.json
[analytics]
feedback_links_enabled = false
reporting_enabled = false

View File

@@ -5,18 +5,18 @@
-- https://groups.google.com/g/signalk/c/W2H15ODCic4
--
-- Description:
-- Insert data into table metadata from API using PostgREST
-- Insert data into table metrics from API using PostgREST
-- TimescaleDB Hypertable to store signalk metrics
-- pgsql functions to generate logbook, stays, moorages
-- Insert data into table api.metadata from API using PostgREST
-- Insert data into table api.metrics from API using PostgREST
-- TimescaleDB Hypertable to store signalk metrics on table api.metrics
-- pgsql functions to generate logbook, stays, moorages from table api.metrics
-- CRON functions to process logbook, stays, moorages
-- python functions for geo reverse and send notification via email and/or pushover
-- python functions for geo reverse and send notification via email, pushover, telegram
-- Views statistics, timelapse, monitoring, logs
-- Always store time in UTC
---------------------------------------------------------------------------
-- vessels signalk -(POST)-> metadata -> metadata_upsert -(trigger)-> metadata_upsert_trigger_fn (INSERT or UPDATE)
-- vessels signalk -(POST)-> metrics -> metrics -(trigger)-> metrics_fn new log,stay,moorage
-- vessels signalk -(POST)-> metadata -> metadata_upsert_trigger -(BEFORE INSERT)-> metadata_upsert_trigger_fn (INSERT or UPDATE)
-- vessels signalk -(POST)-> metrics -> metrics_trigger -(BEFORE INSERT)-> metrics_trigger_fn (INSERT or UPDATE new log,stay)
---------------------------------------------------------------------------
@@ -51,13 +51,20 @@ CREATE DATABASE signalk;
ALTER DATABASE signalk WITH CONNECTION LIMIT = 100;
-- Set timezone to UTC
ALTER DATABASE signalk SET TIMEZONE='UTC';
-- Set datestyle output
ALTER DATABASE signalk SET datestyle TO "ISO, DMY";
-- Set intervalstyle output
ALTER DATABASE signalk SET intervalstyle TO 'iso_8601';
-- connect to the DB
\c signalk
-- Schema
CREATE SCHEMA IF NOT EXISTS api;
COMMENT ON SCHEMA api IS 'api schema expose to postgrest';
COMMENT ON SCHEMA api IS
$$PostgSail API
A RESTful API that serves PostgSail data using postgrest.$$;
-- Revoke default privileges to all public functions
ALTER DEFAULT PRIVILEGES REVOKE EXECUTE ON FUNCTIONS FROM PUBLIC;

View File

@@ -8,7 +8,7 @@
---------------------------------------------------------------------------
-- Metadata from signalk
CREATE TABLE IF NOT EXISTS api.metadata(
id SERIAL PRIMARY KEY,
id INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
name TEXT NULL,
mmsi NUMERIC NULL,
client_id TEXT NULL,
@@ -20,10 +20,10 @@ CREATE TABLE IF NOT EXISTS api.metadata(
ship_type NUMERIC NULL,
plugin_version TEXT NOT NULL,
signalk_version TEXT NOT NULL,
time TIMESTAMP WITHOUT TIME ZONE NOT NULL, -- should be rename to last_update !?
time TIMESTAMPTZ NOT NULL, -- should be rename to last_update !?
active BOOLEAN DEFAULT True, -- trigger monitor online/offline
created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW()
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Description
COMMENT ON TABLE
@@ -42,8 +42,7 @@ CREATE INDEX metadata_name_idx ON api.metadata (name);
CREATE TYPE status AS ENUM ('sailing', 'motoring', 'moored', 'anchored');
-- Table api.metrics
CREATE TABLE IF NOT EXISTS api.metrics (
time TIMESTAMP WITHOUT TIME ZONE NOT NULL,
--client_id VARCHAR(255) NOT NULL REFERENCES api.metadata(client_id) ON DELETE RESTRICT,
time TIMESTAMPTZ NOT NULL,
client_id TEXT NULL,
vessel_id TEXT NOT NULL REFERENCES api.metadata(vessel_id) ON DELETE RESTRICT,
latitude DOUBLE PRECISION NULL,
@@ -55,8 +54,8 @@ CREATE TABLE IF NOT EXISTS api.metrics (
status status NULL,
metrics jsonb NULL,
--CONSTRAINT valid_client_id CHECK (length(client_id) > 10),
CONSTRAINT valid_latitude CHECK (latitude >= -90 and latitude <= 90),
CONSTRAINT valid_longitude CHECK (longitude >= -180 and longitude <= 180),
--CONSTRAINT valid_latitude CHECK (latitude >= -90 and latitude <= 90),
--CONSTRAINT valid_longitude CHECK (longitude >= -180 and longitude <= 180),
PRIMARY KEY (time, vessel_id)
);
-- Description
@@ -96,64 +95,65 @@ SELECT create_hypertable('api.metrics', 'time', chunk_time_interval => INTERVAL
-- Check unused index
CREATE TABLE IF NOT EXISTS api.logbook(
id SERIAL PRIMARY KEY,
--client_id VARCHAR(255) NOT NULL REFERENCES api.metadata(client_id) ON DELETE RESTRICT,
--client_id VARCHAR(255) NULL,
id INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
vessel_id TEXT NOT NULL REFERENCES api.metadata(vessel_id) ON DELETE RESTRICT,
active BOOLEAN DEFAULT false,
name VARCHAR(255),
_from VARCHAR(255),
name TEXT,
_from_moorage_id INT NULL,
_from TEXT,
_from_lat DOUBLE PRECISION NULL,
_from_lng DOUBLE PRECISION NULL,
_to VARCHAR(255),
_to_moorage_id INT NULL,
_to TEXT,
_to_lat DOUBLE PRECISION NULL,
_to_lng DOUBLE PRECISION NULL,
--track_geom Geometry(LINESTRING)
track_geom geometry(LINESTRING,4326) NULL,
track_geog geography(LINESTRING) NULL,
track_geojson JSON NULL,
track_gpx XML NULL,
_from_time TIMESTAMP WITHOUT TIME ZONE NOT NULL,
_to_time TIMESTAMP WITHOUT TIME ZONE NULL,
track_geojson JSONB NULL,
_from_time TIMESTAMPTZ NOT NULL,
_to_time TIMESTAMPTZ NULL,
distance NUMERIC, -- meters?
duration INTERVAL, -- duration in days and hours?
avg_speed DOUBLE PRECISION NULL,
max_speed DOUBLE PRECISION NULL,
max_wind_speed DOUBLE PRECISION NULL,
notes TEXT NULL,
extra JSONB NULL
notes TEXT NULL, -- remarks
extra JSONB NULL -- computed signalk metrics of interest
);
-- Description
COMMENT ON TABLE
api.logbook
IS 'Stores generated logbook';
COMMENT ON COLUMN api.logbook.distance IS 'in NM';
COMMENT ON COLUMN api.logbook.extra IS 'computed signalk metrics of interest, runTime, currentLevel, etc';
COMMENT ON COLUMN api.logbook.duration IS 'Best to use standard ISO 8601';
-- Index todo!
CREATE INDEX logbook_vessel_id_idx ON api.logbook (vessel_id);
CREATE INDEX logbook_from_moorage_id_idx ON api.logbook (_from_moorage_id);
CREATE INDEX logbook_to_moorage_id_idx ON api.logbook (_to_moorage_id);
CREATE INDEX ON api.logbook USING GIST ( track_geom );
COMMENT ON COLUMN api.logbook.track_geom IS 'postgis geometry type EPSG:4326 Unit: degres';
CREATE INDEX ON api.logbook USING GIST ( track_geog );
COMMENT ON COLUMN api.logbook.track_geog IS 'postgis geography type default SRID 4326 Unit: degres';
-- Otherwise -- ERROR: Only lon/lat coordinate systems are supported in geography.
COMMENT ON COLUMN api.logbook.track_geojson IS 'store the geojson track metrics data, can not depend api.metrics table, should be generate from linetring to save disk space?';
COMMENT ON COLUMN api.logbook.track_gpx IS 'store the gpx track metrics data, can not depend api.metrics table, should be generate from linetring to save disk space?';
COMMENT ON COLUMN api.logbook.track_geojson IS 'store generated geojson with track metrics data using with LineString and Point features, we can not depend api.metrics table';
---------------------------------------------------------------------------
-- Stays
-- virtual logbook by boat?
CREATE TABLE IF NOT EXISTS api.stays(
id SERIAL PRIMARY KEY,
--client_id VARCHAR(255) NOT NULL REFERENCES api.metadata(client_id) ON DELETE RESTRICT,
--client_id VARCHAR(255) NULL,
id INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
vessel_id TEXT NOT NULL REFERENCES api.metadata(vessel_id) ON DELETE RESTRICT,
active BOOLEAN DEFAULT false,
name VARCHAR(255),
moorage_id INT NULL,
name TEXT,
latitude DOUBLE PRECISION NULL,
longitude DOUBLE PRECISION NULL,
geog GEOGRAPHY(POINT) NULL,
arrived TIMESTAMP WITHOUT TIME ZONE NOT NULL,
departed TIMESTAMP WITHOUT TIME ZONE,
arrived TIMESTAMPTZ NOT NULL,
departed TIMESTAMPTZ,
duration INTERVAL, -- duration in days and hours?
stay_code INT DEFAULT 1, -- REFERENCES api.stays_at(stay_code),
notes TEXT NULL
@@ -165,21 +165,20 @@ COMMENT ON TABLE
-- Index
CREATE INDEX stays_vessel_id_idx ON api.stays (vessel_id);
CREATE INDEX stays_moorage_id_idx ON api.stays (moorage_id);
CREATE INDEX ON api.stays USING GIST ( geog );
COMMENT ON COLUMN api.stays.geog IS 'postgis geography type default SRID 4326 Unit: degres';
-- With other SRID ERROR: Only lon/lat coordinate systems are supported in geography.
COMMENT ON COLUMN api.stays.duration IS 'Best to use standard ISO 8601';
---------------------------------------------------------------------------
-- Moorages
-- virtual logbook by boat?
CREATE TABLE IF NOT EXISTS api.moorages(
id SERIAL PRIMARY KEY,
--client_id VARCHAR(255) NOT NULL REFERENCES api.metadata(client_id) ON DELETE RESTRICT,
--client_id VARCHAR(255) NULL,
id INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
vessel_id TEXT NOT NULL REFERENCES api.metadata(vessel_id) ON DELETE RESTRICT,
name TEXT,
country TEXT, -- todo need to update reverse_geocode_py_fn
stay_id INT NOT NULL, -- needed?
country TEXT,
stay_code INT DEFAULT 1, -- needed? REFERENCES api.stays_at(stay_code)
stay_duration INTERVAL NULL,
reference_count INT DEFAULT 1,
@@ -187,7 +186,9 @@ CREATE TABLE IF NOT EXISTS api.moorages(
longitude DOUBLE PRECISION NULL,
geog GEOGRAPHY(POINT) NULL,
home_flag BOOLEAN DEFAULT false,
notes TEXT NULL
notes TEXT NULL,
overpass JSONB NULL,
nominatim JSONB NULL
);
-- Description
COMMENT ON TABLE
@@ -199,18 +200,19 @@ CREATE INDEX moorages_vessel_id_idx ON api.moorages (vessel_id);
CREATE INDEX ON api.moorages USING GIST ( geog );
COMMENT ON COLUMN api.moorages.geog IS 'postgis geography type default SRID 4326 Unit: degres';
-- With other SRID ERROR: Only lon/lat coordinate systems are supported in geography.
COMMENT ON COLUMN api.moorages.stay_duration IS 'Best to use standard ISO 8601';
---------------------------------------------------------------------------
-- Stay Type
CREATE TABLE IF NOT EXISTS api.stays_at(
stay_code INTEGER NOT NULL,
stay_code INTEGER UNIQUE NOT NULL,
description TEXT NOT NULL
);
-- Description
COMMENT ON TABLE api.stays_at IS 'Stay Type';
-- Insert default possible values
INSERT INTO api.stays_at(stay_code, description) VALUES
(1, 'Unknow'),
(1, 'Unknown'),
(2, 'Anchor'),
(3, 'Mooring Buoy'),
(4, 'Dock');
@@ -254,7 +256,8 @@ CREATE FUNCTION metadata_upsert_trigger_fn() RETURNS trigger AS $metadata_upsert
ship_type = NEW.ship_type,
plugin_version = NEW.plugin_version,
signalk_version = NEW.signalk_version,
time = NEW.time,
-- time = NEW.time, ignore the time sent by the vessel as it is out of sync sometimes.
time = NOW(), -- overwrite the time sent by the vessel
active = true
WHERE id = metadata_id;
RETURN NULL; -- Ignore insert
@@ -263,7 +266,9 @@ CREATE FUNCTION metadata_upsert_trigger_fn() RETURNS trigger AS $metadata_upsert
-- set vessel_id from jwt if not present in INSERT query
NEW.vessel_id := current_setting('vessel.id');
END IF;
-- Insert new vessel metadata and
-- Ignore and overwrite the time sent by the vessel
NEW.time := NOW();
-- Insert new vessel metadata
RETURN NEW; -- Insert new vessel metadata
END IF;
END;
@@ -326,13 +331,13 @@ COMMENT ON TRIGGER
DROP FUNCTION IF EXISTS metrics_trigger_fn;
CREATE FUNCTION metrics_trigger_fn() RETURNS trigger AS $metrics$
DECLARE
previous_status varchar;
previous_time TIMESTAMP WITHOUT TIME ZONE;
stay_code integer;
logbook_id integer;
stay_id integer;
valid_status BOOLEAN;
previous_metric record;
stay_code INTEGER;
logbook_id INTEGER;
stay_id INTEGER;
valid_status BOOLEAN := False;
_vessel_id TEXT;
distance BOOLEAN := False;
BEGIN
--RAISE NOTICE 'metrics_trigger_fn';
--RAISE WARNING 'metrics_trigger_fn [%] [%]', current_setting('vessel.id', true), NEW;
@@ -343,40 +348,76 @@ CREATE FUNCTION metrics_trigger_fn() RETURNS trigger AS $metrics$
END IF;
-- Boat metadata are check using api.metrics REFERENCES to api.metadata
-- Fetch the latest entry to compare status against the new status to be insert
SELECT coalesce(m.status, 'moored'), m.time INTO previous_status, previous_time
SELECT * INTO previous_metric
FROM api.metrics m
WHERE m.vessel_id IS NOT NULL
AND m.vessel_id = current_setting('vessel.id', true)
ORDER BY m.time DESC LIMIT 1;
--RAISE NOTICE 'Metrics Status, New:[%] Previous:[%]', NEW.status, previous_status;
IF previous_time = NEW.time THEN
--RAISE NOTICE 'Metrics Status, New:[%] Previous:[%]', NEW.status, previous_metric.status;
IF previous_metric.time = NEW.time THEN
-- Ignore entry if same time
RAISE WARNING 'Metrics Ignoring metric, vessel_id [%], duplicate time [%] = [%]', NEW.vessel_id, previous_time, NEW.time;
RAISE WARNING 'Metrics Ignoring metric, vessel_id [%], duplicate time [%] = [%]', NEW.vessel_id, previous_metric.time, NEW.time;
RETURN NULL;
END IF;
IF previous_time > NEW.time THEN
IF previous_metric.time > NEW.time THEN
-- Ignore entry if new time is later than previous time
RAISE WARNING 'Metrics Ignoring metric, vessel_id [%], new time is older [%] > [%]', NEW.vessel_id, previous_time, NEW.time;
RAISE WARNING 'Metrics Ignoring metric, vessel_id [%], new time is older than previous_metric.time [%] > [%]', NEW.vessel_id, previous_metric.time, NEW.time;
RETURN NULL;
END IF;
-- Check if latitude or longitude are type double
--IF public.isdouble(NEW.latitude::TEXT) IS False OR public.isdouble(NEW.longitude::TEXT) IS False THEN
-- -- Ignore entry if null latitude,longitude
-- RAISE WARNING 'Metrics Ignoring metric, vessel_id [%], not a double type for latitude or longitude [%] [%]', NEW.vessel_id, NEW.latitude, NEW.longitude;
-- RETURN NULL;
--END IF;
-- Check if latitude or longitude are null
IF NEW.latitude IS NULL OR NEW.longitude IS NULL THEN
-- Ignore entry if null latitude,longitude
RAISE WARNING 'Metrics Ignoring metric, vessel_id [%], null latitude,longitude [%] [%]', NEW.vessel_id, NEW.latitude, NEW.longitude;
RAISE WARNING 'Metrics Ignoring metric, vessel_id [%], null latitude or longitude [%] [%]', NEW.vessel_id, NEW.latitude, NEW.longitude;
RETURN NULL;
END IF;
-- Check if status is null
IF NEW.status IS NULL THEN
-- Check if valid latitude
IF NEW.latitude >= 90 OR NEW.latitude <= -90 THEN
-- Ignore entry if invalid latitude,longitude
RAISE WARNING 'Metrics Ignoring metric, vessel_id [%], invalid latitude >= 90 OR <= -90 [%] [%]', NEW.vessel_id, NEW.latitude, NEW.longitude;
RETURN NULL;
END IF;
-- Check if valid longitude
IF NEW.longitude >= 180 OR NEW.longitude <= -180 THEN
-- Ignore entry if invalid latitude,longitude
RAISE WARNING 'Metrics Ignoring metric, vessel_id [%], invalid longitude >= 180 OR <= -180 [%] [%]', NEW.vessel_id, NEW.latitude, NEW.longitude;
RETURN NULL;
END IF;
-- Check if valid longitude and latitude not close to -0.0000001 from Victron Cerbo
IF NEW.latitude = NEW.longitude THEN
-- Ignore entry if latitude,longitude are equal
RAISE WARNING 'Metrics Ignoring metric, vessel_id [%], latitude and longitude are equal [%] [%]', NEW.vessel_id, NEW.latitude, NEW.longitude;
RETURN NULL;
END IF;
-- Check distance with previous point is > 10km
--SELECT ST_Distance(
-- ST_MakePoint(NEW.latitude,NEW.longitude)::geography,
-- ST_MakePoint(previous_metric.latitude,previous_metric.longitude)::geography) > 10000 INTO distance;
--IF distance IS True THEN
-- RAISE WARNING 'Metrics Ignoring metric, distance between previous metric and new metric is too large, vessel_id [%] distance[%]', NEW.vessel_id, distance;
-- RETURN NULL;
--END IF;
-- Check if status is null but speed is over 3knots set status to sailing
IF NEW.status IS NULL AND NEW.speedoverground >= 3 THEN
RAISE WARNING 'Metrics Unknown NEW.status, vessel_id [%], null status, set to sailing because of speedoverground is +3 from [%]', NEW.vessel_id, NEW.status;
NEW.status := 'sailing';
-- Check if status is null then set status to default moored
ELSIF NEW.status IS NULL THEN
RAISE WARNING 'Metrics Unknown NEW.status, vessel_id [%], null status, set to default moored from [%]', NEW.vessel_id, NEW.status;
NEW.status := 'moored';
END IF;
IF previous_status IS NULL THEN
IF previous_metric.status IS NULL THEN
IF NEW.status = 'anchored' THEN
RAISE WARNING 'Metrics Unknown previous_status from vessel_id [%], [%] set to default current status [%]', NEW.vessel_id, previous_status, NEW.status;
previous_status := NEW.status;
RAISE WARNING 'Metrics Unknown previous_metric.status from vessel_id [%], [%] set to default current status [%]', NEW.vessel_id, previous_metric.status, NEW.status;
previous_metric.status := NEW.status;
ELSE
RAISE WARNING 'Metrics Unknown previous_status from vessel_id [%], [%] set to default status moored vs [%]', NEW.vessel_id, previous_status, NEW.status;
previous_status := 'moored';
RAISE WARNING 'Metrics Unknown previous_metric.status from vessel_id [%], [%] set to default status moored vs [%]', NEW.vessel_id, previous_metric.status, NEW.status;
previous_metric.status := 'moored';
END IF;
-- Add new stay as no previous entry exist
INSERT INTO api.stays
@@ -386,7 +427,7 @@ CREATE FUNCTION metrics_trigger_fn() RETURNS trigger AS $metrics$
-- Add stay entry to process queue for further processing
INSERT INTO process_queue (channel, payload, stored, ref_id)
VALUES ('new_stay', stay_id, now(), current_setting('vessel.id', true));
RAISE WARNING 'Metrics Insert first stay as no previous metrics exist, stay_id %', stay_id;
RAISE WARNING 'Metrics Insert first stay as no previous metrics exist, stay_id stay_id [%] [%] [%]', stay_id, NEW.status, NEW.time;
END IF;
-- Check if status is valid enum
SELECT NEW.status::name = any(enum_range(null::status)::name[]) INTO valid_status;
@@ -395,13 +436,19 @@ CREATE FUNCTION metrics_trigger_fn() RETURNS trigger AS $metrics$
RAISE WARNING 'Metrics Ignoring metric, invalid status [%]', NEW.status;
RETURN NULL;
END IF;
-- Check if speedOverGround is valid value
IF NEW.speedoverground >= 40 THEN
-- Ignore entry as speedOverGround is invalid
RAISE WARNING 'Metrics Ignoring metric, vessel_id [%], speedOverGround is invalid, over 40 < [%]', NEW.vessel_id, NEW.speedoverground;
RETURN NULL;
END IF;
-- Check the state and if any previous/current entry
-- If change of state and new status is sailing or motoring
IF previous_status::TEXT <> NEW.status::TEXT AND
( (NEW.status::TEXT = 'sailing' AND previous_status::TEXT <> 'motoring')
OR (NEW.status::TEXT = 'motoring' AND previous_status::TEXT <> 'sailing') ) THEN
RAISE WARNING 'Metrics Update status, try new logbook, New:[%] Previous:[%]', NEW.status, previous_status;
IF previous_metric.status::TEXT <> NEW.status::TEXT AND
( (NEW.status::TEXT = 'sailing' AND previous_metric.status::TEXT <> 'motoring')
OR (NEW.status::TEXT = 'motoring' AND previous_metric.status::TEXT <> 'sailing') ) THEN
RAISE WARNING 'Metrics Update status, try new logbook, New:[%] Previous:[%]', NEW.status, previous_metric.status;
-- Start new log
logbook_id := public.trip_in_progress_fn(current_setting('vessel.id', true)::TEXT);
IF logbook_id IS NULL THEN
@@ -409,7 +456,7 @@ CREATE FUNCTION metrics_trigger_fn() RETURNS trigger AS $metrics$
(vessel_id, active, _from_time, _from_lat, _from_lng)
VALUES (current_setting('vessel.id', true), true, NEW.time, NEW.latitude, NEW.longitude)
RETURNING id INTO logbook_id;
RAISE WARNING 'Metrics Insert new logbook, logbook_id %', logbook_id;
RAISE WARNING 'Metrics Insert new logbook, logbook_id [%] [%] [%]', logbook_id, NEW.status, NEW.time;
ELSE
UPDATE api.logbook
SET
@@ -418,7 +465,7 @@ CREATE FUNCTION metrics_trigger_fn() RETURNS trigger AS $metrics$
_to_lat = NEW.latitude,
_to_lng = NEW.longitude
WHERE id = logbook_id;
RAISE WARNING 'Metrics Existing Logbook logbook_id [%] [%] [%]', logbook_id, NEW.status, NEW.time;
RAISE WARNING 'Metrics Existing logbook logbook_id [%] [%] [%]', logbook_id, NEW.status, NEW.time;
END IF;
-- End current stay
@@ -429,20 +476,20 @@ CREATE FUNCTION metrics_trigger_fn() RETURNS trigger AS $metrics$
active = false,
departed = NEW.time
WHERE id = stay_id;
RAISE WARNING 'Metrics Updating Stay end current stay_id [%] [%] [%]', stay_id, NEW.status, NEW.time;
-- Add moorage entry to process queue for further processing
-- Add stay entry to process queue for further processing
INSERT INTO process_queue (channel, payload, stored, ref_id)
VALUES ('new_moorage', stay_id, now(), current_setting('vessel.id', true));
VALUES ('new_stay', stay_id, now(), current_setting('vessel.id', true));
RAISE WARNING 'Metrics Updating Stay end current stay_id [%] [%] [%]', stay_id, NEW.status, NEW.time;
ELSE
RAISE WARNING 'Metrics Invalid stay_id [%] [%]', stay_id, NEW.time;
END IF;
-- If change of state and new status is moored or anchored
ELSIF previous_status::TEXT <> NEW.status::TEXT AND
( (NEW.status::TEXT = 'moored' AND previous_status::TEXT <> 'anchored')
OR (NEW.status::TEXT = 'anchored' AND previous_status::TEXT <> 'moored') ) THEN
ELSIF previous_metric.status::TEXT <> NEW.status::TEXT AND
( (NEW.status::TEXT = 'moored' AND previous_metric.status::TEXT <> 'anchored')
OR (NEW.status::TEXT = 'anchored' AND previous_metric.status::TEXT <> 'moored') ) THEN
-- Start new stays
RAISE WARNING 'Metrics Update status, try new stay, New:[%] Previous:[%]', NEW.status, previous_status;
RAISE WARNING 'Metrics Update status, try new stay, New:[%] Previous:[%]', NEW.status, previous_metric.status;
stay_id := public.stay_in_progress_fn(current_setting('vessel.id', true)::TEXT);
IF stay_id IS NULL THEN
RAISE WARNING 'Metrics Inserting new stay [%]', NEW.status;
@@ -456,15 +503,14 @@ CREATE FUNCTION metrics_trigger_fn() RETURNS trigger AS $metrics$
(vessel_id, active, arrived, latitude, longitude, stay_code)
VALUES (current_setting('vessel.id', true), true, NEW.time, NEW.latitude, NEW.longitude, stay_code)
RETURNING id INTO stay_id;
-- Add stay entry to process queue for further processing
INSERT INTO process_queue (channel, payload, stored, ref_id)
VALUES ('new_stay', stay_id, now(), current_setting('vessel.id', true));
RAISE WARNING 'Metrics Insert new stay, stay_id [%] [%] [%]', stay_id, NEW.status, NEW.time;
ELSE
RAISE WARNING 'Metrics Invalid stay_id [%] [%]', stay_id, NEW.time;
UPDATE api.stays
SET
active = false,
departed = NEW.time
departed = NEW.time,
notes = 'Invalid stay?'
WHERE id = stay_id;
END IF;
@@ -483,9 +529,9 @@ CREATE FUNCTION metrics_trigger_fn() RETURNS trigger AS $metrics$
WHERE id = logbook_id;
-- Add logbook entry to process queue for later processing
INSERT INTO process_queue (channel, payload, stored, ref_id)
VALUEs ('new_logbook', logbook_id, now(), current_setting('vessel.id', true));
VALUES ('new_logbook', logbook_id, now(), current_setting('vessel.id', true));
ELSE
RAISE WARNING 'Metrics Invalid logbook_id [%] [%]', logbook_id, NEW.time;
RAISE WARNING 'Metrics Invalid logbook_id [%] [%] [%]', logbook_id, NEW.status, NEW.time;
END IF;
END IF;
RETURN NEW; -- Finally insert the actual new metric
@@ -504,3 +550,57 @@ CREATE TRIGGER metrics_trigger BEFORE INSERT ON api.metrics
COMMENT ON TRIGGER
metrics_trigger ON api.metrics
IS 'BEFORE INSERT ON api.metrics run function metrics_trigger_fn';
-- Function update of name and stay_code on logbook and stays reference
DROP FUNCTION IF EXISTS moorage_update_trigger_fn;
CREATE FUNCTION moorage_update_trigger_fn() RETURNS trigger AS $moorage_update$
DECLARE
BEGIN
RAISE NOTICE 'moorages_update_trigger_fn [%]', NEW;
IF ( OLD.name != NEW.name) THEN
UPDATE api.logbook SET _from = NEW.name WHERE _from_moorage_id = NEW.id;
UPDATE api.logbook SET _to = NEW.name WHERE _to_moorage_id = NEW.id;
END IF;
IF ( OLD.stay_code != NEW.stay_code) THEN
UPDATE api.stays SET stay_code = NEW.stay_code WHERE moorage_id = NEW.id;
END IF;
RETURN NULL; -- result is ignored since this is an AFTER trigger
END;
$moorage_update$ LANGUAGE plpgsql;
-- Description
COMMENT ON FUNCTION
public.moorage_update_trigger_fn
IS 'Automatic update of name and stay_code on logbook and stays reference';
-- Triggers moorage update after update
CREATE TRIGGER moorage_update_trigger AFTER UPDATE ON api.moorages
FOR EACH ROW EXECUTE FUNCTION moorage_update_trigger_fn();
-- Description
COMMENT ON TRIGGER moorage_update_trigger
ON api.moorages
IS 'Automatic update of name and stay_code on logbook and stays reference';
-- Function delete logbook and stays reference when delete a moorage
DROP FUNCTION IF EXISTS moorage_delete_trigger_fn;
CREATE FUNCTION moorage_delete_trigger_fn() RETURNS trigger AS $moorage_delete$
DECLARE
BEGIN
RAISE NOTICE 'moorages_delete_trigger_fn [%]', OLD;
DELETE FROM api.stays WHERE moorage_id = OLD.id;
DELETE FROM api.logbook WHERE _from_moorage_id = OLD.id;
DELETE FROM api.logbook WHERE _to_moorage_id = OLD.id;
RETURN OLD; -- result is ignored since this is an AFTER trigger
END;
$moorage_delete$ LANGUAGE plpgsql;
-- Description
COMMENT ON FUNCTION
public.moorage_delete_trigger_fn
IS 'Automatic delete logbook and stays reference when delete a moorage';
-- Triggers moorage delete
CREATE TRIGGER moorage_delete_trigger BEFORE DELETE ON api.moorages
FOR EACH ROW EXECUTE FUNCTION moorage_delete_trigger_fn();
-- Description
COMMENT ON TRIGGER moorage_delete_trigger
ON api.moorages
IS 'Automatic update of name and stay_code on logbook and stays reference';

View File

@@ -16,44 +16,72 @@ CREATE OR REPLACE FUNCTION api.timelapse_fn(
IN end_log INTEGER DEFAULT NULL,
IN start_date TEXT DEFAULT NULL,
IN end_date TEXT DEFAULT NULL,
OUT geojson JSON) RETURNS JSON AS $timelapse$
OUT geojson JSONB) RETURNS JSONB AS $timelapse$
DECLARE
_geojson jsonb;
BEGIN
-- TODO using jsonb pgsql function instead of python
-- Using sub query to force id order by
-- Merge GIS track_geom into a GeoJSON MultiLineString
IF start_log IS NOT NULL AND public.isnumeric(start_log::text) AND public.isnumeric(end_log::text) THEN
SELECT jsonb_agg(track_geojson->'features') INTO _geojson
FROM api.logbook
WHERE id >= start_log
AND id <= end_log;
--raise WARNING 'by log _geojson %' , _geojson;
WITH logbook as (
SELECT track_geom
FROM api.logbook
WHERE id >= start_log
AND id <= end_log
AND track_geom IS NOT NULL
ORDER BY _from_time ASC
)
SELECT ST_AsGeoJSON(geo.*) INTO _geojson FROM (
SELECT ST_Collect(
ARRAY(
SELECT track_geom FROM logbook))
) as geo;
--raise WARNING 'by log id _geojson %' , _geojson;
ELSIF start_date IS NOT NULL AND public.isdate(start_date::text) AND public.isdate(end_date::text) THEN
SELECT jsonb_agg(track_geojson->'features') INTO _geojson
FROM api.logbook
WHERE _from_time >= start_log::TIMESTAMP WITHOUT TIME ZONE
AND _to_time <= end_date::TIMESTAMP WITHOUT TIME ZONE + interval '23 hours 59 minutes';
WITH logbook as (
SELECT track_geom
FROM api.logbook
WHERE _from_time >= start_log::TIMESTAMPTZ
AND _to_time <= end_date::TIMESTAMPTZ + interval '23 hours 59 minutes'
AND track_geom IS NOT NULL
ORDER BY _from_time ASC
)
SELECT ST_AsGeoJSON(geo.*) INTO _geojson FROM (
SELECT ST_Collect(
ARRAY(
SELECT track_geom FROM logbook))
) as geo;
--raise WARNING 'by date _geojson %' , _geojson;
ELSE
SELECT jsonb_agg(track_geojson->'features') INTO _geojson
FROM api.logbook;
WITH logbook as (
SELECT track_geom
FROM api.logbook
WHERE track_geom IS NOT NULL
ORDER BY _from_time ASC
)
SELECT ST_AsGeoJSON(geo.*) INTO _geojson FROM (
SELECT ST_Collect(
ARRAY(
SELECT track_geom FROM logbook))
) as geo;
--raise WARNING 'all result _geojson %' , _geojson;
END IF;
-- Return a GeoJSON filter on Point
-- Return a GeoJSON MultiLineString
-- result _geojson [null, null]
--raise WARNING 'result _geojson %' , _geojson;
SELECT json_build_object(
'type', 'FeatureCollection',
'features', public.geojson_py_fn(_geojson, 'LineString'::TEXT) ) INTO geojson;
'features', ARRAY[_geojson] ) INTO geojson;
END;
$timelapse$ LANGUAGE plpgsql;
-- Description
COMMENT ON FUNCTION
api.timelapse_fn
IS 'Export to geojson feature point with Time and courseOverGroundTrue properties';
IS 'Export all selected logs geometry `track_geom` to a geojson as MultiLineString with empty properties';
-- export_logbook_geojson_fn
DROP FUNCTION IF EXISTS api.export_logbook_geojson_fn;
CREATE FUNCTION api.export_logbook_geojson_fn(IN _id integer, OUT geojson JSON) RETURNS JSON AS $export_logbook_geojson$
CREATE FUNCTION api.export_logbook_geojson_fn(IN _id integer, OUT geojson JSONB) RETURNS JSONB AS $export_logbook_geojson$
-- validate with geojson.io
DECLARE
logbook_rec record;
@@ -77,7 +105,7 @@ $export_logbook_geojson$ LANGUAGE plpgsql;
-- Description
COMMENT ON FUNCTION
api.export_logbook_geojson_fn
IS 'Export a log entry to geojson feature linestring and multipoint';
IS 'Export a log entry to geojson with features LineString and Point';
-- Generate GPX XML file output
-- https://opencpn.org/OpenCPN/info/gpxvalidation.html
@@ -86,65 +114,224 @@ DROP FUNCTION IF EXISTS api.export_logbook_gpx_fn;
CREATE OR REPLACE FUNCTION api.export_logbook_gpx_fn(IN _id INTEGER) RETURNS pg_catalog.xml
AS $export_logbook_gpx$
DECLARE
log_rec record;
app_settings jsonb;
BEGIN
-- If _id is is not NULL and > 0
IF _id IS NULL OR _id < 1 THEN
RAISE WARNING '-> export_logbook_geojson_fn invalid input %', _id;
RAISE WARNING '-> export_logbook_gpx_fn invalid input %', _id;
RETURN '';
END IF;
-- Gather log details _from_time and _to_time
SELECT * INTO log_rec
FROM
api.logbook l
WHERE l.id = _id;
-- Ensure the query is successful
IF log_rec.vessel_id IS NULL THEN
RAISE WARNING '-> export_logbook_gpx_fn invalid logbook %', _id;
RETURN '';
END IF;
-- Generate XML
-- Gather url from app settings
app_settings := get_app_url_fn();
--RAISE DEBUG '-> logbook_update_gpx_fn app_settings %', app_settings;
-- Generate GPX XML, extract Point features from geojson.
RETURN xmlelement(name gpx,
xmlattributes( '1.1' as version,
'PostgSAIL' as creator,
'http://www.topografix.com/GPX/1/1' as xmlns,
'http://www.opencpn.org' as "xmlns:opencpn",
'https://iot.openplotter.cloud' as "xmlns:postgsail",
app_settings->>'app.url' as "xmlns:postgsail",
'http://www.w3.org/2001/XMLSchema-instance' as "xmlns:xsi",
'http://www.garmin.com/xmlschemas/GpxExtensions/v3' as "xmlns:gpxx",
'http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www8.garmin.com/xmlschemas/GpxExtensionsv3.xsd' as "xsi:schemaLocation"),
xmlelement(name metadata,
xmlelement(name link, xmlattributes(app_settings->>'app.url' as href),
xmlelement(name text, 'PostgSail'))),
xmlelement(name trk,
xmlelement(name name, log_rec.name),
xmlelement(name desc, log_rec.notes),
xmlelement(name link, xmlattributes(concat('https://iot.openplotter.cloud/log/', log_rec.id) as href),
xmlelement(name text, log_rec.name)),
xmlelement(name extensions, xmlelement(name "postgsail:log_id", 1),
xmlelement(name "postgsail:link", concat('https://iot.openplotter.cloud/log/', log_rec.id)),
xmlelement(name name, l.name),
xmlelement(name desc, l.notes),
xmlelement(name link, xmlattributes(concat(app_settings->>'app.url', '/log/', l.id) as href),
xmlelement(name text, l.name)),
xmlelement(name extensions, xmlelement(name "postgsail:log_id", l.id),
xmlelement(name "postgsail:link", concat(app_settings->>'app.url', '/log/', l.id)),
xmlelement(name "opencpn:guid", uuid_generate_v4()),
xmlelement(name "opencpn:viz", '1'),
xmlelement(name "opencpn:start", log_rec._from_time),
xmlelement(name "opencpn:end", log_rec._to_time)
xmlelement(name "opencpn:start", l._from_time),
xmlelement(name "opencpn:end", l._to_time)
),
xmlelement(name trkseg, xmlagg(
xmlelement(name trkpt,
xmlattributes(latitude as lat, longitude as lon),
xmlelement(name time, time)
xmlattributes(features->'geometry'->'coordinates'->1 as lat, features->'geometry'->'coordinates'->0 as lon),
xmlelement(name time, features->'properties'->>'time')
)))))::pg_catalog.xml
FROM api.metrics m
WHERE m.latitude IS NOT NULL
AND m.longitude IS NOT NULL
AND m.time >= log_rec._from_time::TIMESTAMP WITHOUT TIME ZONE
AND m.time <= log_rec._to_time::TIMESTAMP WITHOUT TIME ZONE
AND vessel_id = log_rec.vessel_id;
-- ERROR: column "m.time" must appear in the GROUP BY clause or be used in an aggregate function at character 2304
--ORDER BY m.time ASC;
END;
FROM api.logbook l, jsonb_array_elements(track_geojson->'features') AS features
WHERE features->'geometry'->>'type' = 'Point'
AND l.id = _id
GROUP BY l.name,l.notes,l.id;
END;
$export_logbook_gpx$ LANGUAGE plpgsql;
-- Description
COMMENT ON FUNCTION
api.export_logbook_gpx_fn
IS 'Export a log entry to GPX XML format';
-- Generate KML XML file output
-- https://developers.google.com/kml/documentation/kml_tut
-- TODO https://developers.google.com/kml/documentation/time#timespans
DROP FUNCTION IF EXISTS api.export_logbook_kml_fn;
CREATE OR REPLACE FUNCTION api.export_logbook_kml_fn(IN _id INTEGER) RETURNS pg_catalog.xml
AS $export_logbook_kml$
DECLARE
logbook_rec record;
BEGIN
-- If _id is is not NULL and > 0
IF _id IS NULL OR _id < 1 THEN
RAISE WARNING '-> export_logbook_kml_fn invalid input %', _id;
return '';
END IF;
-- Gather log details
SELECT * INTO logbook_rec
FROM api.logbook WHERE id = _id;
-- Ensure the query is successful
IF logbook_rec.vessel_id IS NULL THEN
RAISE WARNING '-> export_logbook_kml_fn invalid logbook %', _id;
return '';
END IF;
-- Extract POINT from LINESTRING to generate KML XML
RETURN xmlelement(name kml,
xmlattributes( '1.0' as version,
'PostgSAIL' as creator,
'http://www.w3.org/2005/Atom' as "xmlns:atom",
'http://www.opengis.net/kml/2.2' as "xmlns",
'http://www.google.com/kml/ext/2.2' as "xmlns:gx",
'http://www.opengis.net/kml/2.2' as "xmlns:kml"),
xmlelement(name "Document",
xmlelement(name name, logbook_rec.name),
xmlelement(name "Placemark",
xmlelement(name name, logbook_rec.notes),
ST_AsKML(logbook_rec.track_geom)::pg_catalog.xml)
))::pg_catalog.xml
FROM api.logbook WHERE id = _id;
END;
$export_logbook_kml$ LANGUAGE plpgsql;
-- Description
COMMENT ON FUNCTION
api.export_logbook_kml_fn
IS 'Export a log entry to KML XML format';
DROP FUNCTION IF EXISTS api.export_logbooks_gpx_fn;
CREATE OR REPLACE FUNCTION api.export_logbooks_gpx_fn(
IN start_log INTEGER DEFAULT NULL,
IN end_log INTEGER DEFAULT NULL) RETURNS pg_catalog.xml
AS $export_logbooks_gpx$
declare
merged_jsonb jsonb;
app_settings jsonb;
BEGIN
-- Merge GIS track_geom of geometry type Point into a jsonb array format
IF start_log IS NOT NULL AND public.isnumeric(start_log::text) AND public.isnumeric(end_log::text) THEN
SELECT jsonb_agg(
jsonb_build_object('coordinates', f->'geometry'->'coordinates', 'time', f->'properties'->>'time')
) INTO merged_jsonb
FROM (
SELECT jsonb_array_elements(track_geojson->'features') AS f
FROM api.logbook
WHERE id >= start_log
AND id <= end_log
AND track_geojson IS NOT NULL
ORDER BY _from_time ASC
) AS sub
WHERE (f->'geometry'->>'type') = 'Point';
ELSE
SELECT jsonb_agg(
jsonb_build_object('coordinates', f->'geometry'->'coordinates', 'time', f->'properties'->>'time')
) INTO merged_jsonb
FROM (
SELECT jsonb_array_elements(track_geojson->'features') AS f
FROM api.logbook
WHERE track_geojson IS NOT NULL
ORDER BY _from_time ASC
) AS sub
WHERE (f->'geometry'->>'type') = 'Point';
END IF;
--RAISE WARNING '-> export_logbooks_gpx_fn _jsonb %' , _jsonb;
-- Gather url from app settings
app_settings := get_app_url_fn();
--RAISE WARNING '-> export_logbooks_gpx_fn app_settings %', app_settings;
-- Generate GPX XML, extract Point features from geojson.
RETURN xmlelement(name gpx,
xmlattributes( '1.1' as version,
'PostgSAIL' as creator,
'http://www.topografix.com/GPX/1/1' as xmlns,
'http://www.opencpn.org' as "xmlns:opencpn",
app_settings->>'app.url' as "xmlns:postgsail"),
xmlelement(name metadata,
xmlelement(name link, xmlattributes(app_settings->>'app.url' as href),
xmlelement(name text, 'PostgSail'))),
xmlelement(name trk,
xmlelement(name name, 'logbook name'),
xmlelement(name trkseg, xmlagg(
xmlelement(name trkpt,
xmlattributes(features->'coordinates'->1 as lat, features->'coordinates'->0 as lon),
xmlelement(name time, features->'properties'->>'time')
)))))::pg_catalog.xml
FROM jsonb_array_elements(merged_jsonb) AS features;
END;
$export_logbooks_gpx$ LANGUAGE plpgsql;
-- Description
COMMENT ON FUNCTION
api.export_logbooks_gpx_fn
IS 'Export a logs entries to GPX XML format';
DROP FUNCTION IF EXISTS api.export_logbooks_kml_fn;
CREATE OR REPLACE FUNCTION api.export_logbooks_kml_fn(
IN start_log INTEGER DEFAULT NULL,
IN end_log INTEGER DEFAULT NULL) RETURNS pg_catalog.xml
AS $export_logbooks_kml$
DECLARE
_geom geometry;
app_settings jsonb;
BEGIN
-- Merge GIS track_geom into a GeoJSON MultiLineString
IF start_log IS NOT NULL AND public.isnumeric(start_log::text) AND public.isnumeric(end_log::text) THEN
WITH logbook as (
SELECT track_geom
FROM api.logbook
WHERE id >= start_log
AND id <= end_log
AND track_geom IS NOT NULL
ORDER BY _from_time ASC
)
SELECT ST_Collect(
ARRAY(
SELECT track_geom FROM logbook))
into _geom;
ELSE
WITH logbook as (
SELECT track_geom
FROM api.logbook
WHERE track_geom IS NOT NULL
ORDER BY _from_time ASC
)
SELECT ST_Collect(
ARRAY(
SELECT track_geom FROM logbook))
into _geom;
--raise WARNING 'all result _geojson %' , _geojson;
END IF;
-- Extract POINT from LINESTRING to generate KML XML
RETURN xmlelement(name kml,
xmlattributes( '1.0' as version,
'PostgSAIL' as creator,
'http://www.w3.org/2005/Atom' as "xmlns:atom",
'http://www.opengis.net/kml/2.2' as "xmlns",
'http://www.google.com/kml/ext/2.2' as "xmlns:gx",
'http://www.opengis.net/kml/2.2' as "xmlns:kml"),
xmlelement(name "Document",
xmlelement(name name, 'logbook name'),
xmlelement(name "Placemark",
ST_AsKML(_geom)::pg_catalog.xml
)
)
)::pg_catalog.xml;
END;
$export_logbooks_kml$ LANGUAGE plpgsql;
-- Description
COMMENT ON FUNCTION
api.export_logbooks_kml_fn
IS 'Export a logs entries to KML XML format';
-- Find all log from and to moorage geopoint within 100m
DROP FUNCTION IF EXISTS api.find_log_from_moorage_fn;
CREATE OR REPLACE FUNCTION api.find_log_from_moorage_fn(IN _id INTEGER, OUT geojson JSON) RETURNS JSON AS $find_log_from_moorage$
@@ -311,6 +498,32 @@ COMMENT ON FUNCTION
api.logs_by_month_fn
IS 'logbook by month for web charts';
-- logs_by_day_fn
DROP FUNCTION IF EXISTS api.logs_by_day_fn;
CREATE FUNCTION api.logs_by_day_fn(OUT charts JSONB) RETURNS JSONB AS $logs_by_day$
DECLARE
data JSONB;
BEGIN
-- Query logs by day
SELECT json_object_agg(day,count) INTO data
FROM (
SELECT
to_char(date_trunc('day', _from_time), 'D') as day,
count(*) as count
FROM api.logbook
GROUP BY day
ORDER BY day
) AS t;
-- Merge jsonb to get all 7 days
SELECT '{"01": 0, "02": 0, "03": 0, "04": 0, "05": 0, "06": 0, "07": 0}'::jsonb ||
data::jsonb INTO charts;
END;
$logs_by_day$ LANGUAGE plpgsql;
-- Description
COMMENT ON FUNCTION
api.logs_by_day_fn
IS 'logbook by day for web charts';
-- moorage_geojson_fn
DROP FUNCTION IF EXISTS api.export_moorages_geojson_fn;
CREATE FUNCTION api.export_moorages_geojson_fn(OUT geojson JSONB) RETURNS JSONB AS $export_moorages_geojson$
@@ -323,10 +536,11 @@ CREATE FUNCTION api.export_moorages_geojson_fn(OUT geojson JSONB) RETURNS JSONB
json_agg(ST_AsGeoJSON(m.*)::JSON) as moorages_geojson
FROM
( SELECT
id,name,
id,name,stay_code,
EXTRACT(DAY FROM justify_hours ( stay_duration )) AS Total_Stay,
geog
FROM api.moorages
WHERE geog IS NOT NULL
) AS m
)
) INTO geojson;
@@ -340,14 +554,17 @@ COMMENT ON FUNCTION
DROP FUNCTION IF EXISTS api.export_moorages_gpx_fn;
CREATE FUNCTION api.export_moorages_gpx_fn() RETURNS pg_catalog.xml AS $export_moorages_gpx$
DECLARE
app_settings jsonb;
BEGIN
-- Gather url from app settings
app_settings := get_app_url_fn();
-- Generate XML
RETURN xmlelement(name gpx,
xmlattributes( '1.1' as version,
'PostgSAIL' as creator,
'http://www.topografix.com/GPX/1/1' as xmlns,
'http://www.opencpn.org' as "xmlns:opencpn",
'https://iot.openplotter.cloud' as "xmlns:postgsail",
app_settings->>'app.url' as "xmlns:postgsail",
'http://www.w3.org/2001/XMLSchema-instance' as "xmlns:xsi",
'http://www.garmin.com/xmlschemas/GpxExtensions/v3' as "xmlns:gpxx",
'http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www8.garmin.com/xmlschemas/GpxExtensionsv3.xsd' as "xsi:schemaLocation"),
@@ -359,23 +576,211 @@ CREATE FUNCTION api.export_moorages_gpx_fn() RETURNS pg_catalog.xml AS $export_m
concat('Last Stayed On: ', 'TODO last seen',
E'\nTotal Stays: ', m.stay_duration,
E'\nTotal Arrivals and Departures: ', m.reference_count,
E'\nLink: ', concat('https://iot.openplotter.cloud/moorage/', m.id)),
E'\nLink: ', concat(app_settings->>'app.url','/moorage/', m.id)),
xmlelement(name "opencpn:guid", uuid_generate_v4())),
xmlelement(name sym, 'anchor'),
xmlelement(name type, 'WPT'),
xmlelement(name link, xmlattributes(concat('https://iot.openplotter.cloud/moorage/', m.id) as href),
xmlelement(name link, xmlattributes(concat(app_settings->>'app.url','moorage/', m.id) as href),
xmlelement(name text, m.name)),
xmlelement(name extensions, xmlelement(name "postgsail:mooorage_id", 1),
xmlelement(name "postgsail:link", concat('https://iot.openplotter.cloud/moorage/', m.id)),
xmlelement(name "postgsail:link", concat(app_settings->>'app.url','moorage/', m.id)),
xmlelement(name "opencpn:guid", uuid_generate_v4()),
xmlelement(name "opencpn:viz", '1'),
xmlelement(name "opencpn:scale_min_max", xmlattributes(true as UseScale, 30000 as ScaleMin, 0 as ScaleMax)
))))
)::pg_catalog.xml
FROM api.moorages m;
FROM api.moorages m
WHERE geog IS NOT NULL;
END;
$export_moorages_gpx$ LANGUAGE plpgsql;
-- Description
COMMENT ON FUNCTION
api.export_moorages_gpx_fn
IS 'Export moorages as gpx';
----------------------------------------------------------------------------------------------
-- Statistics
DROP FUNCTION IF EXISTS api.stats_logs_fn;
CREATE OR REPLACE FUNCTION api.stats_logs_fn(
IN start_date TEXT DEFAULT NULL,
IN end_date TEXT DEFAULT NULL,
OUT stats JSON) RETURNS JSON AS $stats_logs$
DECLARE
_start_date TIMESTAMPTZ DEFAULT '1970-01-01';
_end_date TIMESTAMPTZ DEFAULT NOW();
BEGIN
IF start_date IS NOT NULL AND public.isdate(start_date::text) AND public.isdate(end_date::text) THEN
RAISE WARNING '--> stats_fn, filter result stats by date [%]', start_date;
_start_date := start_date::TIMESTAMPTZ;
_end_date := end_date::TIMESTAMPTZ;
END IF;
RAISE NOTICE '--> stats_fn, _start_date [%], _end_date [%]', _start_date, _end_date;
WITH
meta AS (
SELECT m.name FROM api.metadata m ),
logs_view AS (
SELECT *
FROM api.logbook l
WHERE _from_time >= _start_date::TIMESTAMPTZ
AND _to_time <= _end_date::TIMESTAMPTZ + interval '23 hours 59 minutes'
),
first_date AS (
SELECT _from_time as first_date from logs_view ORDER BY first_date ASC LIMIT 1
),
last_date AS (
SELECT _to_time as last_date from logs_view ORDER BY _to_time DESC LIMIT 1
),
max_speed_id AS (
SELECT id FROM logs_view WHERE max_speed = (SELECT max(max_speed) FROM logs_view) ),
max_wind_speed_id AS (
SELECT id FROM logs_view WHERE max_wind_speed = (SELECT max(max_wind_speed) FROM logs_view)),
max_distance_id AS (
SELECT id FROM logs_view WHERE distance = (SELECT max(distance) FROM logs_view)),
max_duration_id AS (
SELECT id FROM logs_view WHERE duration = (SELECT max(duration) FROM logs_view)),
logs_stats AS (
SELECT
count(*) AS count,
max(max_speed) AS max_speed,
max(max_wind_speed) AS max_wind_speed,
max(distance) AS max_distance,
sum(distance) AS sum_distance,
max(duration) AS max_duration,
sum(duration) AS sum_duration
FROM logs_view l )
--select * from logbook;
-- Return a JSON
SELECT jsonb_build_object(
'name', meta.name,
'first_date', first_date.first_date,
'last_date', last_date.last_date,
'max_speed_id', max_speed_id.id,
'max_wind_speed_id', max_wind_speed_id.id,
'max_duration_id', max_duration_id.id,
'max_distance_id', max_distance_id.id)::jsonb || to_jsonb(logs_stats.*)::jsonb INTO stats
FROM max_speed_id, max_wind_speed_id, max_distance_id, max_duration_id,
logs_stats, meta, logs_view, first_date, last_date;
-- TODO Add moorages
END;
$stats_logs$ LANGUAGE plpgsql;
-- Description
COMMENT ON FUNCTION
api.stats_logs_fn
IS 'Logs stats by date';
DROP FUNCTION IF EXISTS api.stats_stays_fn;
CREATE OR REPLACE FUNCTION api.stats_stays_fn(
IN start_date TEXT DEFAULT NULL,
IN end_date TEXT DEFAULT NULL,
OUT stats JSON) RETURNS JSON AS $stats_stays$
DECLARE
_start_date TIMESTAMPTZ DEFAULT '1970-01-01';
_end_date TIMESTAMPTZ DEFAULT NOW();
BEGIN
IF start_date IS NOT NULL AND public.isdate(start_date::text) AND public.isdate(end_date::text) THEN
RAISE NOTICE '--> stats_stays_fn, custom filter result stats by date [%]', start_date;
_start_date := start_date::TIMESTAMPTZ;
_end_date := end_date::TIMESTAMPTZ;
END IF;
RAISE NOTICE '--> stats_stays_fn, _start_date [%], _end_date [%]', _start_date, _end_date;
WITH
moorages_log AS (
SELECT s.id as stays_id, m.id as moorages_id, *
FROM api.stays s, api.moorages m
WHERE arrived >= _start_date::TIMESTAMPTZ
AND departed <= _end_date::TIMESTAMPTZ + interval '23 hours 59 minutes'
AND s.id = m.stay_id
),
home_ports AS (
select count(*) as home_ports from moorages_log m where home_flag is true
),
unique_moorage AS (
select count(*) as unique_moorage from moorages_log m
),
time_at_home_ports AS (
select sum(m.stay_duration) as time_at_home_ports from moorages_log m where home_flag is true
),
sum_stay_duration AS (
select sum(m.stay_duration) as sum_stay_duration from moorages_log m where home_flag is false
),
time_spent_away AS (
select m.stay_code,sum(m.stay_duration) as stay_duration from api.moorages m where home_flag is false group by m.stay_code order by m.stay_code
),
time_spent as (
select jsonb_agg(t.*) as time_spent_away from time_spent_away t
)
-- Return a JSON
SELECT jsonb_build_object(
'home_ports', home_ports.home_ports,
'unique_moorage', unique_moorage.unique_moorage,
'time_at_home_ports', time_at_home_ports.time_at_home_ports,
'sum_stay_duration', sum_stay_duration.sum_stay_duration,
'time_spent_away', time_spent.time_spent_away) INTO stats
FROM moorages_log, home_ports, unique_moorage,
time_at_home_ports, sum_stay_duration, time_spent;
-- TODO Add moorages
END;
$stats_stays$ LANGUAGE plpgsql;
-- Description
COMMENT ON FUNCTION
api.stats_stays_fn
IS 'Stays/Moorages stats by date';
DROP FUNCTION IF EXISTS api.delete_logbook_fn;
CREATE OR REPLACE FUNCTION api.delete_logbook_fn(IN _id integer) RETURNS BOOLEAN AS $delete_logbook$
DECLARE
logbook_rec record;
previous_stays_id numeric;
current_stays_departed text;
current_stays_id numeric;
current_stays_active boolean;
BEGIN
-- If _id is not NULL
IF _id IS NULL OR _id < 1 THEN
RAISE WARNING '-> delete_logbook_fn invalid input %', _id;
RETURN FALSE;
END IF;
SELECT * INTO logbook_rec
FROM api.logbook l
WHERE id = _id;
-- Update logbook
UPDATE api.logbook l
SET notes = 'mark for deletion'
WHERE l.vessel_id = current_setting('vessel.id', false)
AND id = logbook_rec.id;
-- Get related stays
SELECT id,departed,active INTO current_stays_id,current_stays_departed,current_stays_active
FROM api.stays s
WHERE s.vessel_id = current_setting('vessel.id', false)
AND s.arrived = logbook_rec._to_time;
-- Update related stays
UPDATE api.stays s
SET notes = 'mark for deletion'
WHERE s.vessel_id = current_setting('vessel.id', false)
AND s.arrived = logbook_rec._to_time;
-- Find previous stays
SELECT id INTO previous_stays_id
FROM api.stays s
WHERE s.vessel_id = current_setting('vessel.id', false)
AND s.arrived < logbook_rec._to_time
ORDER BY s.arrived DESC LIMIT 1;
-- Update previous stays with the departed time from current stays
-- and set the active state from current stays
UPDATE api.stays
SET departed = current_stays_departed::TIMESTAMPTZ,
active = current_stays_active
WHERE vessel_id = current_setting('vessel.id', false)
AND id = previous_stays_id;
-- Clean up, remove invalid logbook and stay entry
DELETE FROM api.logbook WHERE id = logbook_rec.id;
RAISE WARNING '-> delete_logbook_fn delete logbook [%]', logbook_rec.id;
DELETE FROM api.stays WHERE id = current_stays_id;
RAISE WARNING '-> delete_logbook_fn delete stays [%]', current_stays_id;
-- TODO should we subtract (-1) moorages ref count or reprocess it?!?
RETURN TRUE;
END;
$delete_logbook$ LANGUAGE plpgsql;
-- Description
COMMENT ON FUNCTION
api.delete_logbook_fn
IS 'Delete a logbook and dependency stay';

View File

@@ -11,6 +11,8 @@
-- Views
-- Views are invoked with the privileges of the view owner,
-- make the user_role the views owner.
-- to bypass this limit you need pg15+ with specific settings
-- security_invoker=true,security_barrier=true
---------------------------------------------------------------------------
CREATE VIEW first_metric AS
@@ -23,12 +25,14 @@ CREATE VIEW last_metric AS
FROM api.metrics
ORDER BY time DESC LIMIT 1;
CREATE VIEW trip_in_progress AS
DROP VIEW IF EXISTS public.trip_in_progress;
CREATE VIEW public.trip_in_progress AS
SELECT *
FROM api.logbook
WHERE active IS true;
CREATE VIEW stay_in_progress AS
DROP VIEW IF EXISTS public.stay_in_progress;
CREATE VIEW public.stay_in_progress AS
SELECT *
FROM api.stays
WHERE active IS true;
@@ -38,15 +42,17 @@ CREATE VIEW stay_in_progress AS
DROP VIEW IF EXISTS api.logs_view;
CREATE OR REPLACE VIEW api.logs_view WITH (security_invoker=true,security_barrier=true) AS
SELECT id,
name as "Name",
_from as "From",
_from_time as "Started",
_to as "To",
_to_time as "Ended",
distance as "Distance",
duration as "Duration"
name as "name",
_from as "from",
_from_time as "started",
_to as "to",
_to_time as "ended",
distance as "distance",
duration as "duration",
_from_moorage_id,_to_moorage_id
FROM api.logbook l
WHERE _to_time IS NOT NULL
WHERE name IS NOT NULL
AND _to_time IS NOT NULL
ORDER BY _from_time DESC;
-- Description
COMMENT ON VIEW
@@ -56,15 +62,17 @@ COMMENT ON VIEW
-- Initial try of MATERIALIZED VIEW
CREATE MATERIALIZED VIEW api.logs_mat_view AS
SELECT id,
name as "Name",
_from as "From",
_from_time as "Started",
_to as "To",
_to_time as "Ended",
distance as "Distance",
duration as "Duration"
name as "name",
_from as "from",
_from_time as "started",
_to as "to",
_to_time as "ended",
distance as "distance",
duration as "duration",
_from_moorage_id,_to_moorage_id
FROM api.logbook l
WHERE _to_time IS NOT NULL
WHERE name IS NOT NULL
AND _to_time IS NOT NULL
ORDER BY _from_time DESC;
-- Description
COMMENT ON MATERIALIZED VIEW
@@ -74,19 +82,21 @@ COMMENT ON MATERIALIZED VIEW
DROP VIEW IF EXISTS api.log_view;
CREATE OR REPLACE VIEW api.log_view WITH (security_invoker=true,security_barrier=true) AS
SELECT id,
name as "Name",
_from as "From",
_from_time as "Started",
_to as "To",
_to_time as "Ended",
distance as "Distance",
duration as "Duration",
notes as "Notes",
name as "name",
_from as "from",
_from_time as "started",
_to as "to",
_to_time as "ended",
distance as "distance",
duration as "duration",
notes as "notes",
track_geojson as geojson,
avg_speed as avg_speed,
max_speed as max_speed,
max_wind_speed as max_wind_speed,
extra as extra
extra as extra,
_from_moorage_id as from_moorage_id,
_to_moorage_id as to_moorage_id
FROM api.logbook l
WHERE _to_time IS NOT NULL
ORDER BY _from_time DESC;
@@ -100,30 +110,28 @@ COMMENT ON VIEW
DROP VIEW IF EXISTS api.stays_view;
CREATE OR REPLACE VIEW api.stays_view WITH (security_invoker=true,security_barrier=true) AS
SELECT s.id,
concat(
extract(DAYS FROM (s.departed-s.arrived)::interval),
' days',
--DATE_TRUNC('day', s.departed-s.arrived),
' stay at ',
s.name,
' in ',
RTRIM(TO_CHAR(s.departed, 'Month')),
' ',
TO_CHAR(s.departed, 'YYYY')
) as "name",
s.name AS "moorage",
s.name AS "name",
m.name AS "moorage",
m.id AS "moorage_id",
(s.departed-s.arrived) AS "duration",
sa.description AS "stayed_at",
sa.stay_code AS "stayed_at_id",
s.arrived AS "arrived",
_from.id as "arrived_log_id",
_from._to_moorage_id as "arrived_from_moorage_id",
_from._to as "arrived_from_moorage_name",
s.departed AS "departed",
_to.id AS "departed_log_id",
_to._from_moorage_id AS "departed_to_moorage_id",
_to._from AS "departed_to_moorage_name",
s.notes AS "notes"
FROM api.stays s, api.stays_at sa, api.moorages m
WHERE departed IS NOT NULL
LEFT JOIN api.logbook As _from ON _from._from_moorage_id = m.id
LEFT JOIN api.logbook AS _to ON _to._to_moorage_id = m.id
WHERE s.departed IS NOT NULL
AND s.name IS NOT NULL
AND s.stay_code = sa.stay_code
AND s.id = m.stay_id
AND s.moorage_id = m.id
ORDER BY s.arrived DESC;
-- Description
COMMENT ON VIEW
@@ -133,30 +141,28 @@ COMMENT ON VIEW
DROP VIEW IF EXISTS api.stay_view;
CREATE OR REPLACE VIEW api.stay_view WITH (security_invoker=true,security_barrier=true) AS
SELECT s.id,
concat(
extract(DAYS FROM (s.departed-s.arrived)::interval),
' days',
--DATE_TRUNC('day', s.departed-s.arrived),
' stay at ',
s.name,
' in ',
RTRIM(TO_CHAR(s.departed, 'Month')),
' ',
TO_CHAR(s.departed, 'YYYY')
) as "name",
s.name AS "moorage",
s.name AS "name",
m.name AS "moorage",
m.id AS "moorage_id",
(s.departed-s.arrived) AS "duration",
sa.description AS "stayed_at",
sa.stay_code AS "stayed_at_id",
s.arrived AS "arrived",
_from.id as "arrived_log_id",
_from._to_moorage_id as "arrived_from_moorage_id",
_from._to as "arrived_from_moorage_name",
s.departed AS "departed",
_to.id AS "departed_log_id",
_to._from_moorage_id AS "departed_to_moorage_id",
_to._from AS "departed_to_moorage_name",
s.notes AS "notes"
FROM api.stays s, api.stays_at sa, api.moorages m
WHERE departed IS NOT NULL
LEFT JOIN api.logbook As _from ON _from._from_moorage_id = m.id
LEFT JOIN api.logbook AS _to ON _to._to_moorage_id = m.id
WHERE s.departed IS NOT NULL
AND s.name IS NOT NULL
AND s.stay_code = sa.stay_code
AND s.id = m.stay_id
AND s.moorage_id = m.id
ORDER BY s.arrived DESC;
-- Description
COMMENT ON VIEW
@@ -187,16 +193,20 @@ CREATE OR REPLACE VIEW api.moorages_view WITH (security_invoker=true,security_ba
sa.description AS Default_Stay,
sa.stay_code AS Default_Stay_Id,
EXTRACT(DAY FROM justify_hours ( m.stay_duration )) AS Total_Stay, -- in days
m.stay_duration AS Total_Duration,
m.reference_count AS Arrivals_Departures
-- m.geog
-- m.stay_duration,
-- justify_hours ( m.stay_duration )
FROM api.moorages m, api.stays_at sa
WHERE m.name is not null
-- m.stay_duration is only process on a stay
WHERE m.stay_duration IS NOT NULL
AND m.geog IS NOT NULL
AND m.stay_code = sa.stay_code
GROUP BY m.id,m.name,sa.description,m.stay_duration,m.reference_count,m.geog,sa.stay_code
-- ORDER BY 4 DESC;
ORDER BY m.reference_count DESC;
-- ORDER BY m.reference_count DESC;
ORDER BY m.stay_duration DESC;
-- Description
COMMENT ON VIEW
api.moorages_view
@@ -206,19 +216,39 @@ DROP VIEW IF EXISTS api.moorage_view;
CREATE OR REPLACE VIEW api.moorage_view WITH (security_invoker=true,security_barrier=true) AS -- TODO
SELECT id,
m.name AS Name,
m.stay_code AS Default_Stay,
sa.description AS Default_Stay,
sa.stay_code AS Default_Stay_Id,
m.home_flag AS Home,
EXTRACT(DAY FROM justify_hours ( m.stay_duration )) AS Total_Stay,
m.stay_duration AS Total_Duration,
m.reference_count AS Arrivals_Departures,
m.notes
-- m.geog
FROM api.moorages m
WHERE m.name IS NOT NULL;
FROM api.moorages m, api.stays_at sa
-- m.stay_duration is only process on a stay
WHERE m.stay_duration IS NOT NULL
AND geog IS NOT NULL
AND m.stay_code = sa.stay_code;
-- Description
COMMENT ON VIEW
api.moorage_view
IS 'Moorage details web view';
DROP VIEW IF EXISTS api.moorages_stays_view;
CREATE OR REPLACE VIEW api.moorages_stays_view WITH (security_invoker=true,security_barrier=true) AS -- TODO
SELECT
_to.id AS _to_id,_to._to_time,
_from.id AS _from_id,_from._from_time,
m.stay_code,m.stay_duration,m.id
FROM api.moorages m
LEFT JOIN api.logbook As _from ON _from._from_moorage_id = m.id
LEFT JOIN api.logbook AS _to ON _to._to_moorage_id = m.id
ORDER BY _to._to_time DESC;
-- Description
COMMENT ON VIEW
api.moorages_stays_view
IS 'Moorages stay listing web view';
-- All moorage in 100 meters from the start of a logbook.
-- ST_DistanceSphere Returns minimum distance in meters between two lon/lat points.
--SELECT
@@ -253,15 +283,15 @@ CREATE OR REPLACE VIEW api.stats_logs_view WITH (security_invoker=true,security_
SELECT m.time FROM api.metrics m ORDER BY m.time ASC limit 1),
logbook AS (
SELECT
count(*) AS "Number of Log Entries",
max(l.max_speed) AS "Max Speed",
max(l.max_wind_speed) AS "Max Wind Speed",
sum(l.distance) AS "Total Distance",
sum(l.duration) AS "Total Time Underway",
concat( max(l.distance), ' NM, ', max(l.duration), ' hours') AS "Longest Nonstop Sail"
count(*) AS "number_of_log_entries",
max(l.max_speed) AS "max_speed",
max(l.max_wind_speed) AS "max_wind_speed",
sum(l.distance) AS "total_distance",
sum(l.duration) AS "total_time_underway",
concat( max(l.distance), ' NM, ', max(l.duration), ' hours') AS "longest_nonstop_sail"
FROM api.logbook l)
SELECT
m.name as Name,
m.name AS name,
fm.time AS first,
lm.time AS last,
l.*
@@ -297,10 +327,10 @@ CREATE OR REPLACE VIEW api.stats_moorages_view WITH (security_invoker=true,secur
select sum(m.stay_duration) as time_spent_away from api.moorages m where home_flag is false
)
SELECT
home_ports.home_ports as "Home Ports",
unique_moorage.unique_moorage as "Unique Moorages",
time_at_home_ports.time_at_home_ports "Time Spent at Home Port(s)",
time_spent_away.time_spent_away as "Time Spent Away"
home_ports.home_ports as "home_ports",
unique_moorage.unique_moorage as "unique_moorages",
time_at_home_ports.time_at_home_ports "time_spent_at_home_port(s)",
time_spent_away.time_spent_away as "time_spent_away"
FROM home_ports, unique_moorage, time_at_home_ports, time_spent_away;
COMMENT ON VIEW
api.stats_moorages_view
@@ -342,8 +372,8 @@ CREATE VIEW api.monitoring_view WITH (security_invoker=true,security_barrier=tru
metrics-> 'environment.outside.temperature' AS outsideTemperature,
metrics-> 'environment.wind.speedOverGround' AS windSpeedOverGround,
metrics-> 'environment.wind.directionGround' AS windDirectionGround,
metrics-> 'environment.inside.humidity' AS insideHumidity,
metrics-> 'environment.outside.humidity' AS outsideHumidity,
metrics-> 'environment.inside.relativeHumidity' AS insideHumidity,
metrics-> 'environment.outside.relativeHumidity' AS outsideHumidity,
metrics-> 'environment.outside.pressure' AS outsidePressure,
metrics-> 'environment.inside.pressure' AS insidePressure,
metrics-> 'electrical.batteries.House.capacity.stateOfCharge' AS batteryCharge,
@@ -357,7 +387,7 @@ CREATE VIEW api.monitoring_view WITH (security_invoker=true,security_barrier=tru
'longitude', m.longitude
)::jsonb ) AS geojson,
current_setting('vessel.name', false) AS name
FROM api.metrics m
FROM api.metrics m
ORDER BY time DESC LIMIT 1;
COMMENT ON VIEW
api.monitoring_view
@@ -368,7 +398,7 @@ CREATE VIEW api.monitoring_humidity WITH (security_invoker=true,security_barrier
SELECT m.time, key, value
FROM api.metrics m,
jsonb_each_text(m.metrics)
WHERE key ILIKE 'environment.%.humidity'
WHERE key ILIKE 'environment.%.humidity' OR key ILIKE 'environment.%.relativeHumidity'
ORDER BY m.time DESC;
COMMENT ON VIEW
api.monitoring_humidity
@@ -450,3 +480,19 @@ CREATE VIEW api.total_info_view WITH (security_invoker=true,security_barrier=tru
COMMENT ON VIEW
api.total_info_view
IS 'total_info_view web view';
DROP VIEW IF EXISTS api.explore_view;
CREATE VIEW api.explore_view WITH (security_invoker=true,security_barrier=true) AS
-- Expose last metrics
WITH raw_metrics AS (
SELECT m.time, m.metrics
FROM api.metrics m
ORDER BY m.time desc limit 1
)
SELECT raw_metrics.time, key, value
FROM raw_metrics,
jsonb_each_text(raw_metrics.metrics)
ORDER BY key ASC;
COMMENT ON VIEW
api.explore_view
IS 'explore_view web view';

View File

@@ -14,13 +14,13 @@ declare
process_rec record;
begin
-- Check for new logbook pending update
RAISE NOTICE 'cron_process_new_logbook_fn';
RAISE NOTICE 'cron_process_new_logbook_fn init loop';
FOR process_rec in
SELECT * FROM process_queue
WHERE channel = 'new_logbook' AND processed IS NULL
ORDER BY stored ASC LIMIT 100
LOOP
RAISE NOTICE '-> cron_process_new_logbook_fn [%]', process_rec.payload;
RAISE NOTICE 'cron_process_new_logbook_fn processing queue [%] for logbook id [%]', process_rec.id, process_rec.payload;
-- update logbook
PERFORM process_logbook_queue_fn(process_rec.payload::INTEGER);
-- update process_queue table , processed
@@ -28,7 +28,7 @@ begin
SET
processed = NOW()
WHERE id = process_rec.id;
RAISE NOTICE '-> cron_process_new_logbook_fn updated process_queue table [%]', process_rec.id;
RAISE NOTICE 'cron_process_new_logbook_fn processed queue [%] for logbook id [%]', process_rec.id, process_rec.payload;
END LOOP;
END;
$$ language plpgsql;
@@ -43,13 +43,13 @@ declare
process_rec record;
begin
-- Check for new stay pending update
RAISE NOTICE 'cron_process_new_stay_fn';
RAISE NOTICE 'cron_process_new_stay_fn init loop';
FOR process_rec in
SELECT * FROM process_queue
WHERE channel = 'new_stay' AND processed IS NULL
ORDER BY stored ASC LIMIT 100
LOOP
RAISE NOTICE '-> cron_process_new_stay_fn [%]', process_rec.payload;
RAISE NOTICE 'cron_process_new_stay_fn processing queue [%] for stay id [%]', process_rec.id, process_rec.payload;
-- update stay
PERFORM process_stay_queue_fn(process_rec.payload::INTEGER);
-- update process_queue table , processed
@@ -57,7 +57,7 @@ begin
SET
processed = NOW()
WHERE id = process_rec.id;
RAISE NOTICE '-> cron_process_new_stay_fn updated process_queue table [%]', process_rec.id;
RAISE NOTICE 'cron_process_new_stay_fn processed queue [%] for stay id [%]', process_rec.id, process_rec.payload;
END LOOP;
END;
$$ language plpgsql;
@@ -73,13 +73,13 @@ declare
process_rec record;
begin
-- Check for new moorage pending update
RAISE NOTICE 'cron_process_new_moorage_fn';
RAISE NOTICE 'cron_process_new_moorage_fn init loop';
FOR process_rec in
SELECT * FROM process_queue
WHERE channel = 'new_moorage' AND processed IS NULL
ORDER BY stored ASC LIMIT 100
LOOP
RAISE NOTICE '-> cron_process_new_moorage_fn [%]', process_rec.payload;
RAISE NOTICE 'cron_process_new_moorage_fn processing queue [%] for moorage id [%]', process_rec.id, process_rec.payload;
-- update moorage
PERFORM process_moorage_queue_fn(process_rec.payload::INTEGER);
-- update process_queue table , processed
@@ -87,7 +87,7 @@ begin
SET
processed = NOW()
WHERE id = process_rec.id;
RAISE NOTICE '-> cron_process_new_moorage_fn updated process_queue table [%]', process_rec.id;
RAISE NOTICE 'cron_process_new_moorage_fn processed queue [%] for moorage id [%]', process_rec.id, process_rec.payload;
END LOOP;
END;
$$ language plpgsql;
@@ -127,12 +127,12 @@ begin
IF metadata_rec.vessel_id IS NULL OR metadata_rec.vessel_id = '' THEN
RAISE WARNING '-> cron_process_monitor_offline_fn invalid metadata record vessel_id %', vessel_id;
RAISE EXCEPTION 'Invalid metadata'
USING HINT = 'Unknow vessel_id';
USING HINT = 'Unknown vessel_id';
RETURN;
END IF;
PERFORM set_config('vessel.id', metadata_rec.vessel_id, false);
RAISE DEBUG '-> DEBUG cron_process_monitor_offline_fn vessel.id %', current_setting('vessel.id', false);
RAISE NOTICE '-> cron_process_monitor_offline_fn updated api.metadata table to inactive for [%] [%]', metadata_rec.id, metadata_rec.vessel_id;
RAISE NOTICE 'cron_process_monitor_offline_fn updated api.metadata table to inactive for [%] [%]', metadata_rec.id, metadata_rec.vessel_id;
-- Gather email and pushover app settings
--app_settings = get_app_settings_fn();
@@ -182,7 +182,7 @@ begin
IF metadata_rec.vessel_id IS NULL OR metadata_rec.vessel_id = '' THEN
RAISE WARNING '-> cron_process_monitor_online_fn invalid metadata record vessel_id %', vessel_id;
RAISE EXCEPTION 'Invalid metadata'
USING HINT = 'Unknow vessel_id';
USING HINT = 'Unknown vessel_id';
RETURN;
END IF;
PERFORM set_config('vessel.id', metadata_rec.vessel_id, false);
@@ -348,21 +348,6 @@ COMMENT ON FUNCTION
public.cron_vacuum_fn
IS 'init by pg_cron to full vacuum tables on schema api';
-- CRON for clean up job details logs
CREATE FUNCTION job_run_details_cleanup_fn() RETURNS void AS $$
DECLARE
BEGIN
-- Remove job run log older than 3 months
RAISE NOTICE 'job_run_details_cleanup_fn';
DELETE FROM postgres.cron.job_run_details
WHERE start_time <= NOW() AT TIME ZONE 'UTC' - INTERVAL '91 DAYS';
END;
$$ language plpgsql;
-- Description
COMMENT ON FUNCTION
public.job_run_details_cleanup_fn
IS 'init by pg_cron to cleanup job_run_details table on schema public postgres db';
-- CRON for alerts notification
CREATE FUNCTION cron_process_alerts_fn() RETURNS void AS $$
DECLARE
@@ -385,4 +370,177 @@ $$ language plpgsql;
-- Description
COMMENT ON FUNCTION
public.cron_process_alerts_fn
IS 'init by pg_cron to check for alerts, if so perform process_alerts_queue_fn';
IS 'init by pg_cron to check for alerts';
-- CRON for no vessel notification
CREATE FUNCTION cron_process_no_vessel_fn() RETURNS void AS $no_vessel$
DECLARE
no_vessel record;
user_settings jsonb;
BEGIN
-- Check for user with no vessel register
RAISE NOTICE 'cron_process_no_vessel_fn';
FOR no_vessel in
SELECT a.user_id,a.email,a.first
FROM auth.accounts a
WHERE NOT EXISTS (
SELECT *
FROM auth.vessels v
WHERE v.owner_email = a.email)
LOOP
RAISE NOTICE '-> cron_process_no_vessel_rec_fn for [%]', no_vessel;
SELECT json_build_object('email', no_vessel.email, 'recipient', no_vessel.first) into user_settings;
RAISE NOTICE '-> debug cron_process_no_vessel_rec_fn [%]', user_settings;
-- Send notification
PERFORM send_notification_fn('no_vessel'::TEXT, user_settings::JSONB);
END LOOP;
END;
$no_vessel$ language plpgsql;
-- Description
COMMENT ON FUNCTION
public.cron_process_no_vessel_fn
IS 'init by pg_cron, check for user with no vessel register then send notification';
-- CRON for no metadata notification
CREATE FUNCTION cron_process_no_metadata_fn() RETURNS void AS $no_metadata$
DECLARE
no_metadata_rec record;
user_settings jsonb;
BEGIN
-- Check for vessel register but with no metadata
RAISE NOTICE 'cron_process_no_metadata_fn';
FOR no_metadata_rec in
SELECT
a.user_id,a.email,a.first
FROM auth.accounts a, auth.vessels v
WHERE NOT EXISTS (
SELECT *
FROM api.metadata m
WHERE v.vessel_id = m.vessel_id) AND v.owner_email = a.email
LOOP
RAISE NOTICE '-> cron_process_no_metadata_rec_fn for [%]', no_metadata_rec;
SELECT json_build_object('email', no_metadata_rec.email, 'recipient', no_metadata_rec.first) into user_settings;
RAISE NOTICE '-> debug cron_process_no_metadata_rec_fn [%]', user_settings;
-- Send notification
PERFORM send_notification_fn('no_metadata'::TEXT, user_settings::JSONB);
END LOOP;
END;
$no_metadata$ language plpgsql;
-- Description
COMMENT ON FUNCTION
public.cron_process_no_metadata_fn
IS 'init by pg_cron, check for vessel with no metadata then send notification';
-- CRON for no activity notification
CREATE FUNCTION cron_process_no_activity_fn() RETURNS void AS $no_activity$
DECLARE
no_activity_rec record;
user_settings jsonb;
BEGIN
-- Check for vessel with no activity for more than 200 days
RAISE NOTICE 'cron_process_no_activity_fn';
FOR no_activity_rec in
SELECT
v.owner_email,m.name,m.vessel_id,m.time,a.first
FROM auth.accounts a
LEFT JOIN auth.vessels v ON v.owner_email = a.email
LEFT JOIN api.metadata m ON v.vessel_id = m.vessel_id
WHERE m.time < NOW() AT TIME ZONE 'UTC' - INTERVAL '200 DAYS'
LOOP
RAISE NOTICE '-> cron_process_no_activity_rec_fn for [%]', no_activity_rec;
SELECT json_build_object('email', no_activity_rec.owner_email, 'recipient', no_activity_rec.first) into user_settings;
RAISE NOTICE '-> debug cron_process_no_activity_rec_fn [%]', user_settings;
-- Send notification
PERFORM send_notification_fn('no_activity'::TEXT, user_settings::JSONB);
END LOOP;
END;
$no_activity$ language plpgsql;
-- Description
COMMENT ON FUNCTION
public.cron_process_no_activity_fn
IS 'init by pg_cron, check for vessel with no activity for more than 200 days then send notification';
-- CRON for deactivated/deletion
CREATE FUNCTION cron_process_deactivated_fn() RETURNS void AS $deactivated$
DECLARE
no_activity_rec record;
user_settings jsonb;
BEGIN
RAISE NOTICE 'cron_process_deactivated_fn';
-- List accounts with vessel inactivity for more than 1 YEAR
FOR no_activity_rec in
SELECT
v.owner_email,m.name,m.vessel_id,m.time,a.first
FROM auth.accounts a
LEFT JOIN auth.vessels v ON v.owner_email = a.email
LEFT JOIN api.metadata m ON v.vessel_id = m.vessel_id
WHERE m.time < NOW() AT TIME ZONE 'UTC' - INTERVAL '1 YEAR'
LOOP
RAISE NOTICE '-> cron_process_deactivated_rec_fn for inactivity [%]', no_activity_rec;
SELECT json_build_object('email', no_activity_rec.owner_email, 'recipient', no_activity_rec.first) into user_settings;
RAISE NOTICE '-> debug cron_process_deactivated_rec_fn inactivity [%]', user_settings;
-- Send notification
PERFORM send_notification_fn('deactivated'::TEXT, user_settings::JSONB);
--PERFORM public.delete_account_fn(no_activity_rec.owner_email::TEXT, no_activity_rec.vessel_id::TEXT);
END LOOP;
-- List accounts with no vessel metadata for more than 1 YEAR
FOR no_activity_rec in
SELECT
a.user_id,a.email,a.first,a.created_at
FROM auth.accounts a, auth.vessels v
WHERE NOT EXISTS (
SELECT *
FROM api.metadata m
WHERE v.vessel_id = m.vessel_id) AND v.owner_email = a.email
AND v.created_at < NOW() AT TIME ZONE 'UTC' - INTERVAL '1 YEAR'
LOOP
RAISE NOTICE '-> cron_process_deactivated_rec_fn for no metadata [%]', no_activity_rec;
SELECT json_build_object('email', no_activity_rec.owner_email, 'recipient', no_activity_rec.first) into user_settings;
RAISE NOTICE '-> debug cron_process_deactivated_rec_fn no metadata [%]', user_settings;
-- Send notification
PERFORM send_notification_fn('deactivated'::TEXT, user_settings::JSONB);
--PERFORM public.delete_account_fn(no_activity_rec.owner_email::TEXT, no_activity_rec.vessel_id::TEXT);
END LOOP;
-- List accounts with no vessel created for more than 1 YEAR
FOR no_activity_rec in
SELECT a.user_id,a.email,a.first,a.created_at
FROM auth.accounts a
WHERE NOT EXISTS (
SELECT *
FROM auth.vessels v
WHERE v.owner_email = a.email)
AND a.created_at < NOW() AT TIME ZONE 'UTC' - INTERVAL '1 YEAR'
LOOP
RAISE NOTICE '-> cron_process_deactivated_rec_fn for no vessel [%]', no_activity_rec;
SELECT json_build_object('email', no_activity_rec.owner_email, 'recipient', no_activity_rec.first) into user_settings;
RAISE NOTICE '-> debug cron_process_deactivated_rec_fn no vessel [%]', user_settings;
-- Send notification
PERFORM send_notification_fn('deactivated'::TEXT, user_settings::JSONB);
--PERFORM public.delete_account_fn(no_activity_rec.owner_email::TEXT, no_activity_rec.vessel_id::TEXT);
END LOOP;
END;
$deactivated$ language plpgsql;
-- Description
COMMENT ON FUNCTION
public.cron_process_deactivated_fn
IS 'init by pg_cron, check for vessel with no activity for more than 1 year then send notification and delete data';
-- Need to be in the postgres database.
\c postgres
-- CRON for clean up job details logs
CREATE FUNCTION job_run_details_cleanup_fn() RETURNS void AS $$
DECLARE
BEGIN
-- Remove job run log older than 3 months
RAISE NOTICE 'job_run_details_cleanup_fn';
DELETE FROM cron.job_run_details
WHERE start_time <= NOW() AT TIME ZONE 'UTC' - INTERVAL '91 DAYS';
END;
$$ language plpgsql;
-- Description
COMMENT ON FUNCTION
public.job_run_details_cleanup_fn
IS 'init by pg_cron to cleanup job_run_details table on schema public postgres db';

View File

@@ -52,7 +52,7 @@ COMMENT ON TABLE
-- with escape value, eg: E'A\nB\r\nC'
-- https://stackoverflow.com/questions/26638615/insert-line-break-in-postgresql-when-updating-text-field
-- TODO Update notification subject for log entry to 'logbook #NB ...'
INSERT INTO email_templates VALUES
INSERT INTO public.email_templates VALUES
('logbook',
'New Logbook Entry',
E'Hello __RECIPIENT__,\n\nWe just wanted to let you know that you have a new entry on openplotter.cloud: "__LOGBOOK_NAME__"\r\n\r\nSee more details at __APP_URL__/log/__LOGBOOK_LINK__\n\nHappy sailing!\nThe PostgSail Team',
@@ -64,19 +64,19 @@ INSERT INTO email_templates VALUES
'Welcome',
E'Hi!\nYou successfully created an account\nKeep in mind to register your vessel.\n'),
('new_vessel',
'New vessel',
'New boat',
E'Hi!\nHow are you?\n__BOAT__ is now linked to your account.\n',
'New vessel',
'New boat',
E'Hi!\nHow are you?\n__BOAT__ is now linked to your account.\n'),
('monitor_offline',
'Vessel Offline',
'Boat went Offline',
E'__BOAT__ has been offline for more than an hour\r\nFind more details at __APP_URL__/boats\n',
'Vessel Offline',
'Boat went Offline',
E'__BOAT__ has been offline for more than an hour\r\nFind more details at __APP_URL__/boats\n'),
('monitor_online',
'Vessel Online',
'Boat went Online',
E'__BOAT__ just came online\nFind more details at __APP_URL__/boats\n',
'Vessel Online',
'Boat went Offline',
E'__BOAT__ just came online\nFind more details at __APP_URL__/boats\n'),
('new_badge',
'New Badge!',
@@ -112,7 +112,27 @@ INSERT INTO email_templates VALUES
'Telegram bot',
E'Hello __RECIPIENT__,\nCongratulations! You have just connect your account to your vessel, @postgsail_bot.\n\nThe PostgSail Team',
'Telegram bot!',
E'Congratulations!\nYou have just connect your account to your vessel, @postgsail_bot.\n');
E'Congratulations!\nYou have just connect your account to your vessel, @postgsail_bot.\n'),
('no_vessel',
'PostgSail add your boat',
E'Hello __RECIPIENT__,\nYou have created an account on PostgSail but you have not created your boat yet.\nIf you need any assistance I would be happy to help. It is free and an open-source.\nThe PostgSail Team',
'PostgSail next step',
E'Hello,\nYou should create your vessel. Check your email!\n'),
('no_metadata',
'PostgSail connect your boat',
E'Hello __RECIPIENT__,\nYou have created an account on PostgSail but you have not connected your boat yet.\nIf you need any assistance I would be happy to help. It is free and an open-source.\nThe PostgSail Team',
'PostgSail next step',
E'Hello,\nYou should connect your vessel. Check your email!\n'),
('no_activity',
'PostgSail boat inactivity',
E'Hello __RECIPIENT__,\nWe don\'t see any activity on your account, do you need any assistance?\nIf you need any assistance I would be happy to help. It is free and an open-source.\nThe PostgSail Team',
'PostgSail inactivity!',
E'We detected inactivity. Check your email!\n'),
('deactivated',
'PostgSail account deactivated',
E'Hello __RECIPIENT__,\nYour account has been deactivated and all your data has been removed from PostgSail system.',
'PostgSail deactivated!',
E'We removed your account. Check your email!\n');
---------------------------------------------------------------------------
-- Queue handling
@@ -130,12 +150,12 @@ INSERT INTO email_templates VALUES
-- table way
CREATE TABLE IF NOT EXISTS public.process_queue (
id SERIAL PRIMARY KEY,
id INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
channel TEXT NOT NULL,
payload TEXT NOT NULL,
ref_id TEXT NOT NULL,
stored TIMESTAMP WITHOUT TIME ZONE NOT NULL,
processed TIMESTAMP WITHOUT TIME ZONE DEFAULT NULL
stored TIMESTAMPTZ NOT NULL,
processed TIMESTAMPTZ DEFAULT NULL
);
-- Description
COMMENT ON TABLE
@@ -170,6 +190,14 @@ begin
END;
$new_vessel_entry$ language plpgsql;
create function new_vessel_public_fn() returns trigger as $new_vessel_public$
begin
-- Update user settings with a public vessel name
perform api.update_user_preferences_fn('{public_vessel}', NEW.name);
return NEW;
END;
$new_vessel_public$ language plpgsql;
---------------------------------------------------------------------------
-- Tables Application Settings
-- https://dba.stackexchange.com/questions/27296/storing-application-settings-with-different-datatypes#27297

File diff suppressed because it is too large Load Diff

View File

@@ -13,6 +13,23 @@ CREATE SCHEMA IF NOT EXISTS public;
---------------------------------------------------------------------------
-- basic helpers to check type and more
--
CREATE OR REPLACE FUNCTION public.isdouble(text) RETURNS BOOLEAN AS
$isdouble$
DECLARE x DOUBLE PRECISION;
BEGIN
x = $1::DOUBLE PRECISION;
RETURN TRUE;
EXCEPTION WHEN others THEN
RETURN FALSE;
END;
$isdouble$
STRICT
LANGUAGE plpgsql IMMUTABLE;
-- Description
COMMENT ON FUNCTION
public.isdouble
IS 'Check typeof value is double';
CREATE OR REPLACE FUNCTION public.isnumeric(text) RETURNS BOOLEAN AS
$isnumeric$
DECLARE x NUMERIC;
@@ -62,9 +79,9 @@ COMMENT ON FUNCTION
CREATE OR REPLACE FUNCTION public.istimestamptz(text) RETURNS BOOLEAN AS
$isdate$
DECLARE x TIMESTAMP WITHOUT TIME ZONE;
DECLARE x TIMESTAMPTZ;
BEGIN
x = $1::TIMESTAMP WITHOUT TIME ZONE;
x = $1::TIMESTAMPTZ;
RETURN TRUE;
EXCEPTION WHEN others THEN
RETURN FALSE;
@@ -75,7 +92,7 @@ LANGUAGE plpgsql IMMUTABLE;
-- Description
COMMENT ON FUNCTION
public.istimestamptz
IS 'Check typeof value is TIMESTAMP WITHOUT TIME ZONE';
IS 'Check typeof value is TIMESTAMPTZ';
---------------------------------------------------------------------------
-- JSON helpers

View File

@@ -15,9 +15,9 @@ CREATE SCHEMA IF NOT EXISTS public;
--
-- https://github.com/CartoDB/labs-postgresql/blob/master/workshop/plpython.md
--
DROP FUNCTION IF EXISTS reverse_geocode_py_fn;
DROP FUNCTION IF EXISTS reverse_geocode_py_fn;
CREATE OR REPLACE FUNCTION reverse_geocode_py_fn(IN geocoder TEXT, IN lon NUMERIC, IN lat NUMERIC,
OUT geo_name TEXT)
OUT geo JSONB)
AS $reverse_geocode_py$
import requests
@@ -39,40 +39,57 @@ AS $reverse_geocode_py$
plpy.error('Error missing parameters')
return None
# Make the request to the geocoder API
# https://operations.osmfoundation.org/policies/nominatim/
payload = {"lon": lon, "lat": lat, "format": "jsonv2", "zoom": 18}
r = requests.get(url, params=payload)
def georeverse(geocoder, lon, lat, zoom="18"):
# Make the request to the geocoder API
# https://operations.osmfoundation.org/policies/nominatim/
headers = {"Accept-Language": "en-US,en;q=0.5", "User-Agent": "PostgSail", "From": "xbgmsharp@gmail.com"}
payload = {"lon": lon, "lat": lat, "format": "jsonv2", "zoom": zoom, "accept-language": "en"}
# https://nominatim.org/release-docs/latest/api/Reverse/
r = requests.get(url, headers=headers, params=payload)
# Return the full address or nothing if not found
# Option1: If name is null fallback to address field road,neighbourhood,suburb
# Option2: Return the json for future reference like country
if r.status_code == 200 and "name" in r.json():
r_dict = r.json()
if r_dict["name"]:
return r_dict["name"]
elif "address" in r_dict and r_dict["address"]:
if "road" in r_dict["address"] and r_dict["address"]["road"]:
return r_dict["address"]["road"]
elif "neighbourhood" in r_dict["address"] and r_dict["address"]["neighbourhood"]:
return r_dict["address"]["neighbourhood"]
elif "suburb" in r_dict["address"] and r_dict["address"]["suburb"]:
return r_dict["address"]["suburb"]
elif "residential" in r_dict["address"] and r_dict["address"]["residential"]:
return r_dict["address"]["residential"]
elif "village" in r_dict["address"] and r_dict["address"]["village"]:
return r_dict["address"]["village"]
elif "town" in r_dict["address"] and r_dict["address"]["town"]:
return r_dict["address"]["town"]
else:
return 'n/a'
else:
return 'n/a'
else:
plpy.warning('Failed to received a geo full address %s', r.json())
#plpy.error('Failed to received a geo full address %s', r.json())
return 'unknow'
$reverse_geocode_py$ LANGUAGE plpython3u;
# Parse response
# If name is null fallback to address field tags: neighbourhood,suburb
# if none repeat with lower zoom level
if r.status_code == 200 and "name" in r.json():
r_dict = r.json()
#plpy.notice('reverse_geocode_py_fn Parameters [{}] [{}] Response'.format(lon, lat, r_dict))
output = None
country_code = None
if "country_code" in r_dict["address"] and r_dict["address"]["country_code"]:
country_code = r_dict["address"]["country_code"]
if r_dict["name"]:
return { "name": r_dict["name"], "country_code": country_code }
elif "address" in r_dict and r_dict["address"]:
if "neighbourhood" in r_dict["address"] and r_dict["address"]["neighbourhood"]:
return { "name": r_dict["address"]["neighbourhood"], "country_code": country_code }
elif "hamlet" in r_dict["address"] and r_dict["address"]["hamlet"]:
return { "name": r_dict["address"]["hamlet"], "country_code": country_code }
elif "suburb" in r_dict["address"] and r_dict["address"]["suburb"]:
return { "name": r_dict["address"]["suburb"], "country_code": country_code }
elif "residential" in r_dict["address"] and r_dict["address"]["residential"]:
return { "name": r_dict["address"]["residential"], "country_code": country_code }
elif "village" in r_dict["address"] and r_dict["address"]["village"]:
return { "name": r_dict["address"]["village"], "country_code": country_code }
elif "town" in r_dict["address"] and r_dict["address"]["town"]:
return { "name": r_dict["address"]["town"], "country_code": country_code }
elif "amenity" in r_dict["address"] and r_dict["address"]["amenity"]:
return { "name": r_dict["address"]["amenity"], "country_code": country_code }
else:
if (zoom == 15):
plpy.notice('georeverse recursive retry with lower zoom than:[{}], Response [{}]'.format(zoom , r.json()))
return { "name": "n/a", "country_code": country_code }
else:
plpy.notice('georeverse recursive retry with lower zoom than:[{}], Response [{}]'.format(zoom , r.json()))
return georeverse(geocoder, lon, lat, 15)
else:
return { "name": "n/a", "country_code": country_code }
else:
plpy.warning('Failed to received a geo full address %s', r.json())
#plpy.error('Failed to received a geo full address %s', r.json())
return { "name": "unknown", "country_code": "unknown" }
return georeverse(geocoder, lon, lat)
$reverse_geocode_py$ TRANSFORM FOR TYPE jsonb LANGUAGE plpython3u;
-- Description
COMMENT ON FUNCTION
public.reverse_geocode_py_fn
@@ -157,7 +174,7 @@ AS $send_email_py$
# Send the message via our own SMTP server.
try:
# send your message with credentials specified above
with smtplib.SMTP(server_smtp, 25) as server:
with smtplib.SMTP(server_smtp, 587) as server:
if 'app.email_user' in app and app['app.email_user'] \
and 'app.email_pass' in app and app['app.email_pass']:
server.starttls()
@@ -340,6 +357,10 @@ AS $urlencode_py$
import urllib.parse
return urllib.parse.quote(uri, safe="");
$urlencode_py$ LANGUAGE plpython3u IMMUTABLE STRICT;
-- Description
COMMENT ON FUNCTION
public.urlencode_py_fn
IS 'python url encode using plpython3u';
---------------------------------------------------------------------------
-- python
@@ -357,16 +378,14 @@ AS $reverse_geoip_py$
url = f'https://ipapi.co/{_ip}/json/'
r = requests.get(url)
#print(r.text)
# Return something boolean?
#plpy.notice('IP [{}] [{}]'.format(_ip, r.status_code))
if r.status_code == 200:
#plpy.notice('Got [{}] [{}]'.format(r.text, r.status_code))
return r.text;
return r.json();
else:
plpy.error('Failed to get ip details')
return '{}'
$reverse_geoip_py$ LANGUAGE plpython3u;
-- Description
COMMENT ON FUNCTION
public.reverse_geoip_py_fn
@@ -416,3 +435,50 @@ IMMUTABLE STRICT;
COMMENT ON FUNCTION
public.geojson_py_fn
IS 'Parse geojson using plpython3u (should be done in PGSQL)';
DROP FUNCTION IF EXISTS overpass_py_fn;
CREATE OR REPLACE FUNCTION overpass_py_fn(IN lon NUMERIC, IN lat NUMERIC,
OUT geo JSONB) RETURNS JSONB
AS $overpass_py$
"""
Return https://overpass-turbo.eu seamark details within 400m
https://overpass-turbo.eu/s/1D91
https://wiki.openstreetmap.org/wiki/Key:seamark:type
"""
import requests
import json
import urllib.parse
headers = {'User-Agent': 'PostgSail', 'From': 'xbgmsharp@gmail.com'}
payload = """
[out:json][timeout:20];
nwr(around:400.0,{0},{1})->.all;
(
nwr.all["seamark:type"~"(mooring|harbour)"][~"^seamark:.*:category$"~"."];
nwr.all["seamark:type"~"(anchorage|anchor_berth|berth)"];
nwr.all["leisure"="marina"];
nwr.all["natural"~"(bay|beach)"];
);
out tags qt;
""".format(lat, lon)
data = urllib.parse.quote(payload, safe="");
url = f'https://overpass-api.de/api/interpreter?data={data}'.format(data)
r = requests.get(url, headers)
#print(r.text)
#plpy.notice(url)
plpy.notice('overpass-api coord lon[{}] lat[{}] [{}]'.format(lon, lat, r.status_code))
if r.status_code == 200 and "elements" in r.json():
r_dict = r.json()
plpy.notice('overpass-api Got [{}]'.format(r_dict["elements"]))
if r_dict["elements"]:
if "tags" in r_dict["elements"][0] and r_dict["elements"][0]["tags"]:
return r_dict["elements"][0]["tags"]; # return the first element
return '{}'
else:
plpy.notice('overpass-api Failed to get overpass-api details')
return '{}'
$overpass_py$ IMMUTABLE strict TRANSFORM FOR TYPE jsonb LANGUAGE plpython3u;
-- Description
COMMENT ON FUNCTION
public.overpass_py_fn
IS 'Return https://overpass-turbo.eu seamark details within 400m using plpython3u';

View File

@@ -21,17 +21,17 @@ CREATE EXTENSION IF NOT EXISTS "pgcrypto"; -- provides cryptographic functions
DROP TABLE IF EXISTS auth.accounts CASCADE;
CREATE TABLE IF NOT EXISTS auth.accounts (
userid UUID NOT NULL UNIQUE DEFAULT uuid_generate_v4(),
public_id INT UNIQUE NOT NULL GENERATED ALWAYS AS IDENTITY,
user_id TEXT NOT NULL UNIQUE DEFAULT RIGHT(gen_random_uuid()::text, 12),
email CITEXT primary key check ( email ~* '^.+@.+\..+$' ),
first text not null check (length(pass) < 512),
last text not null check (length(pass) < 512),
pass text not null check (length(pass) < 512),
role name not null check (length(role) < 512),
email CITEXT PRIMARY KEY CHECK ( email ~* '^.+@.+\..+$' ),
first TEXT NOT NULL CHECK (length(pass) < 512),
last TEXT NOT NULL CHECK (length(pass) < 512),
pass TEXT NOT NULL CHECK (length(pass) < 512),
role name NOT NULL CHECK (length(role) < 512),
preferences JSONB NULL DEFAULT '{"email_notifications":true}',
created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW(),
connected_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW(),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
connected_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT valid_email CHECK (length(email) > 5), -- Enforce at least 5 char, eg: a@b.io
CONSTRAINT valid_first CHECK (length(first) > 1),
CONSTRAINT valid_last CHECK (length(last) > 1),
@@ -42,11 +42,9 @@ COMMENT ON TABLE
auth.accounts
IS 'users account table';
-- Indexes
-- is unused index?
--CREATE INDEX accounts_role_idx ON auth.accounts (role);
CREATE INDEX accounts_preferences_idx ON auth.accounts using GIN (preferences);
-- is unused index?
--CREATE INDEX accounts_userid_idx ON auth.accounts (userid);
CREATE INDEX accounts_preferences_idx ON auth.accounts USING GIN (preferences);
COMMENT ON COLUMN auth.accounts.first IS 'User first name with CONSTRAINT CHECK';
COMMENT ON COLUMN auth.accounts.last IS 'User last name with CONSTRAINT CHECK';
CREATE TRIGGER accounts_moddatetime
BEFORE UPDATE ON auth.accounts
@@ -59,7 +57,7 @@ COMMENT ON TRIGGER accounts_moddatetime
DROP TABLE IF EXISTS auth.vessels;
CREATE TABLE IF NOT EXISTS auth.vessels (
vessel_id TEXT NOT NULL UNIQUE DEFAULT RIGHT(gen_random_uuid()::text, 12),
vessel_id TEXT NOT NULL UNIQUE DEFAULT RIGHT(gen_random_uuid()::text, 12),
-- user_id TEXT NOT NULL REFERENCES auth.accounts(user_id) ON DELETE RESTRICT,
owner_email CITEXT PRIMARY KEY REFERENCES auth.accounts(email) ON DELETE RESTRICT,
-- mmsi TEXT UNIQUE, -- Should be a numeric range between 100000000 and 800000000.
@@ -67,8 +65,8 @@ CREATE TABLE IF NOT EXISTS auth.vessels (
name TEXT NOT NULL CHECK (length(name) >= 3 AND length(name) < 512),
-- pass text not null check (length(pass) < 512), -- unused
role name not null check (length(role) < 512),
created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW()
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
-- CONSTRAINT valid_length_mmsi CHECK (length(mmsi) < 10 OR length(mmsi) = 0)
CONSTRAINT valid_range_mmsi CHECK (mmsi > 100000000 AND mmsi < 800000000)
);
@@ -77,10 +75,6 @@ COMMENT ON TABLE
auth.vessels
IS 'vessels table link to accounts email user_id column';
-- Indexes
-- is unused index?
--CREATE INDEX vessels_role_idx ON auth.vessels (role);
-- is unused index?
--CREATE INDEX vessels_name_idx ON auth.vessels (name);
CREATE INDEX vessels_vesselid_idx ON auth.vessels (vessel_id);
CREATE TRIGGER vessels_moddatetime
@@ -110,12 +104,26 @@ create constraint trigger ensure_user_role_exists
after insert or update on auth.accounts
for each row
execute procedure auth.check_role_exists();
-- Description
COMMENT ON TRIGGER ensure_user_role_exists
ON auth.accounts
IS 'ensure user role exists';
-- trigger add queue new account
CREATE TRIGGER new_account_entry AFTER INSERT ON auth.accounts
FOR EACH ROW EXECUTE FUNCTION public.new_account_entry_fn();
-- Description
COMMENT ON TRIGGER new_account_entry
ON auth.accounts
IS 'Add new account in process_queue for further processing';
-- trigger add queue new account OTP validation
CREATE TRIGGER new_account_otp_validation_entry AFTER INSERT ON auth.accounts
FOR EACH ROW EXECUTE FUNCTION public.new_account_otp_validation_entry_fn();
-- Description
COMMENT ON TRIGGER new_account_otp_validation_entry
ON auth.accounts
IS 'Add new account OTP validation in process_queue for further processing';
-- trigger check role on vessel
drop trigger if exists ensure_vessel_role_exists on auth.vessels;
@@ -126,6 +134,18 @@ create constraint trigger ensure_vessel_role_exists
-- trigger add queue new vessel
CREATE TRIGGER new_vessel_entry AFTER INSERT ON auth.vessels
FOR EACH ROW EXECUTE FUNCTION public.new_vessel_entry_fn();
-- Description
COMMENT ON TRIGGER new_vessel_entry
ON auth.vessels
IS 'Add new vessel in process_queue for further processing';
-- trigger add new vessel name as public_vessel user configuration
CREATE TRIGGER new_vessel_public AFTER INSERT ON auth.vessels
FOR EACH ROW EXECUTE FUNCTION public.new_vessel_public_fn();
-- Description
COMMENT ON TRIGGER new_vessel_public
ON auth.vessels
IS 'Add new vessel name as public_vessel user configuration';
create or replace function
auth.encrypt_pass() returns trigger as $$
@@ -183,7 +203,10 @@ begin
-- check email and password
select auth.user_role(email, pass) into _role;
if _role is null then
raise invalid_password using message = 'invalid user or password';
-- HTTP/403
--raise invalid_password using message = 'invalid user or password';
-- HTTP/401
raise insufficient_privilege using message = 'invalid user or password';
end if;
-- Get app_jwt_secret

View File

@@ -20,12 +20,21 @@ COMMENT ON COLUMN api.metadata.vessel_id IS 'Link auth.vessels with api.metadata
-- REFERENCE ship type with AIS type ?
-- REFERENCE mmsi MID with country ?
ALTER TABLE api.logbook ADD FOREIGN KEY (_from_moorage_id) REFERENCES api.moorages(id) ON DELETE RESTRICT;
COMMENT ON COLUMN api.logbook._from_moorage_id IS 'Link api.moorages with api.logbook via FOREIGN KEY and REFERENCES';
ALTER TABLE api.logbook ADD FOREIGN KEY (_to_moorage_id) REFERENCES api.moorages(id) ON DELETE RESTRICT;
COMMENT ON COLUMN api.logbook._to_moorage_id IS 'Link api.moorages with api.logbook via FOREIGN KEY and REFERENCES';
ALTER TABLE api.stays ADD FOREIGN KEY (moorage_id) REFERENCES api.moorages(id) ON DELETE RESTRICT;
COMMENT ON COLUMN api.stays.moorage_id IS 'Link api.moorages with api.stays via FOREIGN KEY and REFERENCES';
ALTER TABLE api.stays ADD FOREIGN KEY (stay_code) REFERENCES api.stays_at(stay_code) ON DELETE RESTRICT;
COMMENT ON COLUMN api.stays.stay_code IS 'Link api.stays_at with api.stays via FOREIGN KEY and REFERENCES';
ALTER TABLE api.moorages ADD FOREIGN KEY (stay_code) REFERENCES api.stays_at(stay_code) ON DELETE RESTRICT;
COMMENT ON COLUMN api.moorages.stay_code IS 'Link api.stays_at with api.moorages via FOREIGN KEY and REFERENCES';
-- List vessel
--TODO add geojson with position
DROP VIEW IF EXISTS api.vessels_view;
CREATE OR REPLACE VIEW api.vessels_view AS
CREATE OR REPLACE VIEW api.vessels_view WITH (security_invoker=true,security_barrier=true) AS
WITH metadata AS (
SELECT COALESCE(
(SELECT m.time
@@ -37,8 +46,10 @@ CREATE OR REPLACE VIEW api.vessels_view AS
SELECT
v.name as name,
v.mmsi as mmsi,
v.created_at::timestamp(0) as created_at,
m.last_contact as last_contact
v.created_at as created_at,
m.last_contact as last_contact,
((NOW() AT TIME ZONE 'UTC' - m.last_contact::TIMESTAMPTZ) > INTERVAL '70 MINUTES') as offline,
(NOW() AT TIME ZONE 'UTC' - m.last_contact::TIMESTAMPTZ) as duration
FROM auth.vessels v, metadata m
WHERE v.owner_email = current_setting('user.email');
-- Description
@@ -94,9 +105,10 @@ AS $vessel$
BEGIN
SELECT
jsonb_build_object(
'name', v.name,
'mmsi', coalesce(v.mmsi, null),
'created_at', v.created_at::timestamp(0),
'name', coalesce(m.name, null),
'mmsi', coalesce(m.mmsi, null),
'created_at', v.created_at,
'first_contact', coalesce(m.created_at, null),
'last_contact', coalesce(m.time, null),
'geojson', coalesce(ST_AsGeoJSON(geojson_t.*)::json, null)
)::jsonb || api.vessel_details_fn()::jsonb
@@ -115,7 +127,7 @@ AS $vessel$
latitude IS NOT NULL
AND longitude IS NOT NULL
AND vessel_id = current_setting('vessel.id', false)
ORDER BY time DESC
ORDER BY time DESC LIMIT 1
) AS geojson_t
WHERE
m.vessel_id = current_setting('vessel.id')
@@ -134,14 +146,14 @@ CREATE OR REPLACE FUNCTION api.settings_fn(out settings json) RETURNS JSON
AS $user_settings$
BEGIN
select row_to_json(row)::json INTO settings
from (
select a.email, a.first, a.last, a.preferences, a.created_at,
from (
select a.email, a.first, a.last, a.preferences, a.created_at,
INITCAP(CONCAT (LEFT(first, 1), ' ', last)) AS username,
public.has_vessel_fn() as has_vessel
--public.has_vessel_metadata_fn() as has_vessel_metadata,
from auth.accounts a
where email = current_setting('user.email')
) row;
) row;
END;
$user_settings$ language plpgsql security definer;
-- Description
@@ -156,7 +168,6 @@ AS $version$
_appv TEXT;
_sysv TEXT;
BEGIN
-- Add postgrest version https://postgrest.org/en/v11.2/references/admin.html#server-version
SELECT
value, rtrim(substring(version(), 0, 17)) AS sys_version into _appv,_sysv
FROM app_settings
@@ -164,7 +175,8 @@ AS $version$
RETURN json_build_object('api_version', _appv,
'sys_version', _sysv,
'timescaledb', (SELECT extversion as timescaledb FROM pg_extension WHERE extname='timescaledb'),
'postgis', (SELECT extversion as postgis FROM pg_extension WHERE extname='postgis'));
'postgis', (SELECT extversion as postgis FROM pg_extension WHERE extname='postgis'),
'postgrest', (SELECT rtrim(substring(application_name from 'PostgREST [0-9.]+')) as postgrest FROM pg_stat_activity WHERE application_name ilike '%postgrest%' LIMIT 1));
END;
$version$ language plpgsql security definer;
-- Description
@@ -174,13 +186,13 @@ COMMENT ON FUNCTION
DROP VIEW IF EXISTS api.versions_view;
CREATE OR REPLACE VIEW api.versions_view AS
-- Add postgrest version https://postgrest.org/en/v11.2/references/admin.html#server-version
SELECT
value AS api_version,
--version() as sys_version
rtrim(substring(version(), 0, 17)) AS sys_version,
(SELECT extversion as timescaledb FROM pg_extension WHERE extname='timescaledb'),
(SELECT extversion as postgis FROM pg_extension WHERE extname='postgis')
(SELECT extversion as postgis FROM pg_extension WHERE extname='postgis'),
(SELECT rtrim(substring(application_name from 'PostgREST [0-9.]+')) as postgrest FROM pg_stat_activity WHERE application_name ilike '%postgrest%' limit 1)
FROM app_settings
WHERE name = 'app.version';
-- Description
@@ -230,15 +242,16 @@ $vessel_details$
DECLARE
BEGIN
RETURN ( WITH tbl AS (
SELECT mmsi,ship_type,length,beam,height FROM api.metadata WHERE vessel_id = current_setting('vessel.id', false)
SELECT mmsi,ship_type,length,beam,height,plugin_version FROM api.metadata WHERE vessel_id = current_setting('vessel.id', false)
)
SELECT json_build_object(
'ship_type', (SELECT ais.description FROM aistypes ais, tbl WHERE t.ship_type = ais.id),
'country', (SELECT mid.country FROM mid, tbl WHERE LEFT(cast(mmsi as text), 3)::NUMERIC = mid.id),
'alpha_2', (SELECT o.alpha_2 FROM mid m, iso3166 o, tbl WHERE LEFT(cast(mmsi as text), 3)::NUMERIC = m.id AND m.country_id = o.id),
'ship_type', (SELECT ais.description FROM aistypes ais, tbl t WHERE t.ship_type = ais.id),
'country', (SELECT mid.country FROM mid, tbl t WHERE LEFT(cast(t.mmsi as text), 3)::NUMERIC = mid.id),
'alpha_2', (SELECT o.alpha_2 FROM mid m, iso3166 o, tbl t WHERE LEFT(cast(t.mmsi as text), 3)::NUMERIC = m.id AND m.country_id = o.id),
'length', t.ship_type,
'beam', t.beam,
'height', t.height)
'height', t.height,
'plugin_version', t.plugin_version)
FROM tbl t
);
END;
@@ -251,11 +264,91 @@ COMMENT ON FUNCTION
DROP VIEW IF EXISTS api.eventlogs_view;
CREATE VIEW api.eventlogs_view WITH (security_invoker=true,security_barrier=true) AS
SELECT pq.*
from public.process_queue pq
where ref_id = current_setting('user.id', true)
or ref_id = current_setting('vessel.id', true)
order by id asc;
FROM public.process_queue pq
WHERE ref_id = current_setting('user.id', true)
OR ref_id = current_setting('vessel.id', true)
ORDER BY id ASC;
-- Description
COMMENT ON VIEW
api.eventlogs_view
IS 'Event logs view';
IS 'Event logs view';
DROP FUNCTION IF EXISTS api.update_logbook_observations_fn;
-- Update/Add a specific user observations into logbook
CREATE OR REPLACE FUNCTION api.update_logbook_observations_fn(IN _id INT, IN observations TEXT) RETURNS BOOLEAN AS
$update_logbook_observations$
DECLARE
BEGIN
-- Merge existing observations with the new observations objects
RAISE NOTICE '-> update_logbook_extra_fn id:[%] observations:[%]', _id, observations;
-- { 'observations': { 'seaState': -1, 'cloudCoverage': -1, 'visibility': -1 } }
UPDATE api.logbook SET extra = public.jsonb_recursive_merge(extra, observations::jsonb) WHERE id = _id;
IF FOUND IS True THEN
RETURN True;
END IF;
RETURN False;
END;
$update_logbook_observations$ language plpgsql security definer;
-- Description
COMMENT ON FUNCTION
api.update_logbook_observations_fn
IS 'Update/Add logbook observations jsonb key pair value';
CREATE TYPE public_type AS ENUM ('public_logs', 'public_logs_list', 'public_timelapse', 'public_monitoring', 'public_stats');
CREATE or replace FUNCTION api.ispublic_fn(IN boat TEXT, IN _type TEXT, IN _id INTEGER DEFAULT NULL) RETURNS BOOLEAN AS $ispublic$
DECLARE
vessel TEXT := '^' || boat || '$';
anonymous BOOLEAN := False;
valid_public_type BOOLEAN := False;
public_logs BOOLEAN := False;
BEGIN
-- If boat is not NULL
IF boat IS NULL THEN
RAISE WARNING '-> ispublic_fn invalid input %', boat;
RETURN False;
END IF;
-- Check if public_type is valid enum
SELECT _type::name = any(enum_range(null::public_type)::name[]) INTO valid_public_type;
IF valid_public_type IS False THEN
-- Ignore entry if type is invalid
RAISE WARNING '-> ispublic_fn invalid input type %', _type;
RETURN False;
END IF;
IF _type ~ '^public_(logs|timelapse)$' AND _id IS NOT NULL THEN
WITH log as (
SELECT vessel_id from api.logbook l where l.id = _id
)
SELECT EXISTS (
SELECT l.vessel_id
FROM auth.accounts a, auth.vessels v, jsonb_each_text(a.preferences) as prefs, log l
WHERE v.vessel_id = l.vessel_id
AND a.email = v.owner_email
AND a.preferences->>'public_vessel'::text ~* vessel
AND prefs.key = _type::TEXT
AND prefs.value::BOOLEAN = true
) into anonymous;
RAISE WARNING '-> ispublic_fn public_logs output boat:[%], type:[%], result:[%]', boat, _type, anonymous;
IF anonymous IS True THEN
RETURN True;
END IF;
ELSE
SELECT EXISTS (
SELECT a.email
FROM auth.accounts a, jsonb_each_text(a.preferences) as prefs
WHERE a.preferences->>'public_vessel'::text ~* vessel
AND prefs.key = _type::TEXT
AND prefs.value::BOOLEAN = true
) into anonymous;
RAISE WARNING '-> ispublic_fn output boat:[%], type:[%], result:[%]', boat, _type, anonymous;
IF anonymous IS True THEN
RETURN True;
END IF;
END IF;
RETURN False;
END
$ispublic$ language plpgsql security definer;
-- Description
COMMENT ON FUNCTION
api.ispublic_fn
IS 'Is web page publicly accessible by register boat name and/or logbook id';

View File

@@ -12,8 +12,8 @@ DROP TABLE IF EXISTS auth.otp;
CREATE TABLE IF NOT EXISTS auth.otp (
-- update email type to CITEXT, https://www.postgresql.org/docs/current/citext.html
user_email CITEXT NOT NULL PRIMARY KEY REFERENCES auth.accounts(email) ON DELETE RESTRICT,
otp_pass VARCHAR(10) NOT NULL,
otp_timestamp TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW(),
otp_pass TEXT NOT NULL,
otp_timestamp TIMESTAMPTZ DEFAULT NOW(),
otp_tries SMALLINT NOT NULL DEFAULT '0'
);
-- Description
@@ -115,8 +115,9 @@ COMMENT ON FUNCTION
DROP FUNCTION IF EXISTS api.reset;
CREATE OR REPLACE FUNCTION api.reset(in pass text, in token text, in uuid text) returns BOOLEAN
AS $reset_fn$
DECLARE
DECLARE
_email TEXT := NULL;
_pass TEXT := pass;
BEGIN
-- Check parameters
IF token IS NULL OR uuid IS NULL OR pass IS NULL THEN
@@ -124,25 +125,25 @@ AS $reset_fn$
END IF;
-- Verify token
SELECT auth.verify_otp_fn(token) INTO _email;
IF _email IS NOT NULL THEN
IF _email IS NOT NULL THEN
SELECT email INTO _email FROM auth.accounts WHERE user_id = uuid;
IF _email IS NULL THEN
RETURN False;
END IF;
-- Set user new password
UPDATE auth.accounts
SET pass = pass
SET pass = _pass
WHERE email = _email;
-- Enable email_validation into user preferences
-- Enable email_validation into user preferences
PERFORM api.update_user_preferences_fn('{email_valid}'::TEXT, True::TEXT);
-- Enable email_notifications
PERFORM api.update_user_preferences_fn('{email_notifications}'::TEXT, True::TEXT);
-- Delete token when validated
DELETE FROM auth.otp
WHERE user_email = _email;
RETURN True;
END IF;
RETURN False;
RETURN True;
END IF;
RETURN False;
END;
$reset_fn$ language plpgsql security definer;
-- Description

View File

@@ -15,13 +15,13 @@ select current_database();
--
-- api_anonymous
-- nologin
-- api_anonymous role in the database with which to execute anonymous web requests, limit 10 connections
-- api_anonymous role in the database with which to execute anonymous web requests, limit 20 connections
-- api_anonymous allows JWT token generation with an expiration time via function api.login() from auth.accounts table
create role api_anonymous WITH NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOLOGIN NOBYPASSRLS NOREPLICATION CONNECTION LIMIT 10;
create role api_anonymous WITH NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOLOGIN NOBYPASSRLS NOREPLICATION CONNECTION LIMIT 20;
comment on role api_anonymous is
'The role that PostgREST will switch to when a user is not authenticated.';
-- Limit to 10 connections
--alter user api_anonymous connection limit 10;
-- Limit to 20 connections
--alter user api_anonymous connection limit 20;
grant usage on schema api to api_anonymous;
-- explicitly limit EXECUTE privileges to only signup and login and reset functions
grant execute on function api.login(text,text) to api_anonymous;
@@ -38,6 +38,17 @@ grant execute on function api.pushover_fn(text,text) to api_anonymous;
grant execute on function api.telegram_fn(text,text) to api_anonymous;
grant execute on function api.telegram_otp_fn(text) to api_anonymous;
--grant execute on function api.generate_otp_fn(text) to api_anonymous;
grant execute on function api.ispublic_fn(text,text,integer) to api_anonymous;
grant execute on function api.timelapse_fn to api_anonymous;
-- Allow read on TABLES on API schema
--GRANT SELECT ON TABLE api.metrics,api.logbook,api.moorages,api.stays,api.metadata,api.stays_at TO api_anonymous;
-- Allow read on VIEWS on API schema
--GRANT SELECT ON TABLE api.logs_view,api.moorages_view,api.stays_view TO api_anonymous;
--GRANT SELECT ON TABLE api.log_view,api.moorage_view,api.stay_view,api.vessels_view TO api_anonymous;
GRANT SELECT ON ALL TABLES IN SCHEMA api TO api_anonymous;
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public TO api_anonymous;
--grant execute on function public.st_asgeojson(record,text,integer,boolean) to api_anonymous;
--grant execute on function public.st_makepoint(float,float) to api_anonymous;
-- authenticator
-- login role
@@ -46,25 +57,28 @@ comment on role authenticator is
'Role that serves as an entry-point for API servers such as PostgREST.';
grant api_anonymous to authenticator;
-- Grafana user and role with login, read-only, limit 15 connections
CREATE ROLE grafana WITH NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOBYPASSRLS NOREPLICATION CONNECTION LIMIT 15 LOGIN PASSWORD 'mysecretpassword';
-- Grafana user and role with login, read-only, limit 20 connections
CREATE ROLE grafana WITH NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOBYPASSRLS NOREPLICATION CONNECTION LIMIT 20 LOGIN PASSWORD 'mysecretpassword';
comment on role grafana is
'Role that grafana will use for authenticated web users.';
-- Allow API schema and Tables
GRANT USAGE ON SCHEMA api TO grafana;
-- Allow read on SEQUENCE on API schema
GRANT USAGE, SELECT ON SEQUENCE api.logbook_id_seq,api.metadata_id_seq,api.moorages_id_seq,api.stays_id_seq TO grafana;
GRANT SELECT ON TABLE api.metrics,api.logbook,api.moorages,api.stays,api.metadata TO grafana;
-- Allow read on TABLES on API schema
GRANT SELECT ON TABLE api.metrics,api.logbook,api.moorages,api.stays,api.metadata,api.stays_at TO grafana;
-- Allow read on VIEWS on API schema
GRANT SELECT ON TABLE api.logs_view,api.moorages_view,api.stays_view TO grafana;
GRANT SELECT ON TABLE api.log_view,api.moorage_view,api.stay_view,api.vessels_view TO grafana;
GRANT SELECT ON TABLE api.metrics,api.logbook,api.moorages,api.stays,api.metadata,api.stays_at TO grafana;
GRANT SELECT ON TABLE api.monitoring_view,api.monitoring_view2,api.monitoring_view3 TO grafana;
GRANT SELECT ON TABLE api.monitoring_humidity,api.monitoring_voltage,api.monitoring_temperatures TO grafana;
-- Allow Auth schema and Tables
GRANT USAGE ON SCHEMA auth TO grafana;
GRANT SELECT ON TABLE auth.vessels TO grafana;
GRANT EXECUTE ON FUNCTION public.citext_eq(citext, citext) TO grafana;
-- Grafana_auth authenticator user and role with login, read-only on auth.accounts, limit 15 connections
CREATE ROLE grafana_auth WITH NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOBYPASSRLS NOREPLICATION CONNECTION LIMIT 15 LOGIN PASSWORD 'mysecretpassword';
-- Grafana_auth authenticator user and role with login, read-only on auth.accounts, limit 20 connections
CREATE ROLE grafana_auth WITH NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOBYPASSRLS NOREPLICATION CONNECTION LIMIT 20 LOGIN PASSWORD 'mysecretpassword';
comment on role grafana_auth is
'Role that grafana auth proxy authenticator via apache.';
-- Allow read on VIEWS on API schema
@@ -79,63 +93,41 @@ GRANT EXECUTE ON FUNCTION public.citext_eq(citext, citext) TO grafana_auth;
-- User:
-- nologin, web api only
-- read-only for all and Read-Write on logbook, stays and moorage except for specific (name, notes) COLUMNS
-- read-only for all and Read on logbook, stays and moorage and Write only for specific (name, notes) COLUMNS
CREATE ROLE user_role WITH NOLOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOBYPASSRLS NOREPLICATION;
comment on role user_role is
'Role that PostgREST will switch to for authenticated web users.';
GRANT user_role to authenticator;
GRANT USAGE ON SCHEMA api TO user_role;
-- Allow read on SEQUENCE on API schema
GRANT USAGE, SELECT ON SEQUENCE api.logbook_id_seq,api.metadata_id_seq,api.moorages_id_seq,api.stays_id_seq TO user_role;
-- Allow read on TABLES on API schema
GRANT SELECT ON TABLE api.metrics,api.logbook,api.moorages,api.stays,api.metadata,api.stays_at TO user_role;
GRANT SELECT ON TABLE public.process_queue TO user_role;
-- To check?
GRANT SELECT ON TABLE auth.vessels TO user_role;
-- Allow users to update certain columns
GRANT UPDATE (name, notes) ON api.logbook TO user_role;
GRANT UPDATE (name, notes, stay_code) ON api.stays TO user_role;
-- Allow users to update certain columns on specific TABLES on API schema
GRANT UPDATE (name, _from, _to, notes) ON api.logbook TO user_role;
GRANT UPDATE (name, notes, stay_code, active, departed) ON api.stays TO user_role;
GRANT UPDATE (name, notes, stay_code, home_flag) ON api.moorages TO user_role;
-- Allow users to remove logs and stays
GRANT DELETE ON api.logbook,api.stays,api.moorages TO user_role;
-- Allow EXECUTE on all FUNCTIONS on API and public schema
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA api TO user_role;
-- explicitly limit EXECUTE privileges to pgrest db-pre-request function
--GRANT EXECUTE ON FUNCTION public.check_jwt() TO user_role;
-- Allow others functions or allow all in public !! ??
--GRANT EXECUTE ON FUNCTION api.export_logbook_geojson_linestring_fn(int4) TO user_role;
--GRANT EXECUTE ON FUNCTION public.st_asgeojson(text) TO user_role;
--GRANT EXECUTE ON FUNCTION public.geography_eq(geography, geography) TO user_role;
-- TODO should not be need !! ??
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public TO user_role;
-- pg15 feature security_invoker=true,security_barrier=true
GRANT SELECT ON TABLE api.logs_view,api.moorages_view,api.stays_view TO user_role;
GRANT SELECT ON TABLE api.log_view,api.moorage_view,api.stay_view,api.vessels_view TO user_role;
GRANT SELECT ON TABLE api.monitoring_view,api.monitoring_view2,api.monitoring_view3 TO user_role;
GRANT SELECT ON TABLE api.monitoring_view,api.monitoring_view2,api.monitoring_view3,api.explore_view TO user_role;
GRANT SELECT ON TABLE api.monitoring_humidity,api.monitoring_voltage,api.monitoring_temperatures TO user_role;
GRANT SELECT ON TABLE api.stats_moorages_away_view,api.versions_view TO user_role;
GRANT SELECT ON TABLE api.total_info_view TO user_role;
GRANT SELECT ON TABLE api.stats_logs_view TO user_role;
GRANT SELECT ON TABLE api.stats_moorages_view TO user_role;
GRANT SELECT ON TABLE api.eventlogs_view TO user_role;
-- Update ownership for security user_role as run by web user.
-- Web listing
--ALTER VIEW api.stays_view OWNER TO user_role;
--ALTER VIEW api.moorages_view OWNER TO user_role;
--ALTER VIEW api.logs_view OWNER TO user_role;
--ALTER VIEW api.vessel_p_view OWNER TO user_role;
--ALTER VIEW api.monitoring_view OWNER TO user_role;
-- Remove all permissions except select
--REVOKE UPDATE, TRUNCATE, REFERENCES, DELETE, TRIGGER, INSERT ON TABLE api.stays_view FROM user_role;
--REVOKE UPDATE, TRUNCATE, REFERENCES, DELETE, TRIGGER, INSERT ON TABLE api.moorages_view FROM user_role;
--REVOKE UPDATE, TRUNCATE, REFERENCES, DELETE, TRIGGER, INSERT ON TABLE api.logs_view FROM user_role;
--REVOKE UPDATE, TRUNCATE, REFERENCES, DELETE, TRIGGER, INSERT ON TABLE api.monitoring_view FROM user_role;
-- Allow read and update on VIEWS
-- Web detail view
--ALTER VIEW api.log_view OWNER TO user_role;
-- Remove all permissions except select and update
--REVOKE TRUNCATE, DELETE, TRIGGER, INSERT ON TABLE api.log_view FROM user_role;
ALTER VIEW api.vessels_view OWNER TO user_role;
-- Remove all permissions except select and update
REVOKE TRUNCATE, DELETE, TRIGGER, INSERT ON TABLE api.vessels_view FROM user_role;
GRANT SELECT ON TABLE api.vessels_view TO user_role;
GRANT SELECT ON TABLE api.moorages_stays_view TO user_role;
-- Vessel:
-- nologin
@@ -145,8 +137,10 @@ comment on role vessel_role is
'Role that PostgREST will switch to for authenticated web vessels.';
GRANT vessel_role to authenticator;
GRANT USAGE ON SCHEMA api TO vessel_role;
GRANT INSERT, UPDATE, SELECT ON TABLE api.metrics,api.logbook,api.moorages,api.stays,api.metadata TO vessel_role;
-- Allow read on SEQUENCE on API schema
GRANT USAGE, SELECT ON SEQUENCE api.logbook_id_seq,api.metadata_id_seq,api.moorages_id_seq,api.stays_id_seq TO vessel_role;
-- Allow read/write on TABLES on API schema
GRANT INSERT, UPDATE, SELECT ON TABLE api.metrics,api.logbook,api.moorages,api.stays,api.metadata TO vessel_role;
GRANT INSERT ON TABLE public.process_queue TO vessel_role;
GRANT USAGE, SELECT ON SEQUENCE public.process_queue_id_seq TO vessel_role;
-- explicitly limit EXECUTE privileges to pgrest db-pre-request function
@@ -156,7 +150,8 @@ GRANT EXECUTE ON FUNCTION public.trip_in_progress_fn(text) to vessel_role;
GRANT EXECUTE ON FUNCTION public.stay_in_progress_fn(text) to vessel_role;
-- hypertable get_partition_hash ?!?
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA _timescaledb_internal TO vessel_role;
-- on metrics st_makepoint
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public TO vessel_role;
--- Scheduler:
-- TODO: currently cron function are run as super user, switch to scheduler role.
@@ -228,6 +223,10 @@ CREATE POLICY api_scheduler_role ON api.metrics TO scheduler
CREATE POLICY grafana_role ON api.metrics TO grafana
USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (false);
-- Allow anonymous to select based on the vessel.id
CREATE POLICY api_anonymous_role ON api.metrics TO api_anonymous
USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (false);
-- Be sure to enable row level security on the table
ALTER TABLE api.logbook ENABLE ROW LEVEL SECURITY;
@@ -252,6 +251,10 @@ CREATE POLICY api_scheduler_role ON api.logbook TO scheduler
CREATE POLICY grafana_role ON api.logbook TO grafana
USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (false);
-- Allow anonymous to select based on the vessel.id
CREATE POLICY api_anonymous_role ON api.logbook TO api_anonymous
USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (false);
-- Be sure to enable row level security on the table
ALTER TABLE api.stays ENABLE ROW LEVEL SECURITY;

View File

@@ -19,11 +19,11 @@ SELECT cron.schedule('cron_new_stay', '*/6 * * * *', 'select public.cron_process
--UPDATE cron.job SET database = 'signalk' where jobname = 'cron_new_stay';
-- Create a every 6 minute job cron_process_new_moorage_fn, delay from stay to give time to generate geo reverse location, eg: name
SELECT cron.schedule('cron_new_moorage', '*/7 * * * *', 'select public.cron_process_new_moorage_fn()');
--SELECT cron.schedule('cron_new_moorage', '*/7 * * * *', 'select public.cron_process_new_moorage_fn()');
--UPDATE cron.job SET database = 'signalk' where jobname = 'cron_new_moorage';
-- Create a every 10 minute job cron_process_monitor_offline_fn
SELECT cron.schedule('cron_monitor_offline', '*/10 * * * *', 'select public.cron_process_monitor_offline_fn()');
SELECT cron.schedule('cron_monitor_offline', '*/11 * * * *', 'select public.cron_process_monitor_offline_fn()');
--UPDATE cron.job SET database = 'signalk' where jobname = 'cron_monitor_offline';
-- Create a every 10 minute job cron_process_monitor_online_fn
@@ -64,11 +64,19 @@ SELECT cron.schedule('cron_prune_otp', '*/15 * * * *', 'select public.cron_proce
-- Create a every 11 minute job cron_process_alerts_fn
--SELECT cron.schedule('cron_alerts', '*/11 * * * *', 'select public.cron_process_alerts_fn()');
-- Notifications/Reminders of no vessel & no metadata & no activity
-- At 08:05 on Sunday.
-- At 08:05 on every 4th day-of-month if it's on Sunday.
SELECT cron.schedule('cron_no_vessel', '5 8 */4 * 0', 'select public.cron_process_no_vessel_fn()');
SELECT cron.schedule('cron_no_metadata', '5 8 */4 * 0', 'select public.cron_process_no_metadata_fn()');
SELECT cron.schedule('cron_no_activity', '5 8 */4 * 0', 'select public.cron_process_no_activity_fn()');
-- Cron job settings
UPDATE cron.job SET database = 'signalk';
UPDATE cron.job SET username = 'username'; -- TODO update to scheduler, pending process_queue update
--UPDATE cron.job SET username = 'username' where jobname = 'cron_vacuum'; -- TODO Update to superuser for vaccuum permissions
UPDATE cron.job SET nodename = '/var/run/postgresql/'; -- VS default localhost ??
UPDATE cron.job SET database = 'postgresql' WHERE jobname = 'job_run_details_cleanup_fn';
-- check job lists
SELECT * FROM cron.job;
-- unschedule by job id

View File

@@ -18,7 +18,7 @@ select current_database();
\c signalk
CREATE TABLE public.ne_10m_geography_marine_polys (
gid serial4 NOT NULL,
gid INT GENERATED ALWAYS AS IDENTITY NOT NULL,
featurecla TEXT NULL,
"name" TEXT NULL,
namealt TEXT NULL,

View File

@@ -1 +1 @@
0.2.2
0.5.0

1
openapi.json Normal file

File diff suppressed because one or more lines are too long

View File

@@ -5,4 +5,3 @@ ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get -q update && apt-get -qy upgrade && apt-get -qy install postgresql-client
# Clean up APT when done.
RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

822
tests/index.js Normal file
View File

@@ -0,0 +1,822 @@
'use strict';
/*
* Unit test #1
* Create 2 users and 2 associate vessel with metrics
*
* process.env.PGSAIL_API_URI = from inside the docker
*
* npm install supertest should mocha mochawesome moment
* alias mocha="./node_modules/mocha/bin/_mocha"
* mocha index.js --reporter mochawesome --reporter-options reportDir=/mnt/postgsail/,reportFilename=report_api.html
*
*/
const sleep = ms => new Promise(r => setTimeout(r, ms));
const supertest = require("supertest");
// Deprecated
const should = require("should");
//const chai = require("chai");
//const should = chai.should();
let request = null;
let user_jwt = null;
let vessel_jwt = null;
var moment = require('moment');
const metrics_kapla = require('./metrics_sample_kapla.json');
const metrics_aava = require('./metrics_sample_aava.json');
const fs = require('fs');
// CNAMEs Array
[
{ cname: process.env.PGSAIL_API_URI, name: "PostgSail unit test kapla",
signin: { email: 'demo+kapla@openplotter.cloud', pass: 'test', firstname:'First_kapla', lastname:'Last_kapla'},
login: { email: 'demo+kapla@openplotter.cloud', pass: 'test'},
vessel: { vessel_email: "demo+kapla@openplotter.cloud", vessel_mmsi: "test", vessel_name: "kapla"},
preferences: { key: '{email_notifications}', value: false }, /* Disable email_notifications */
vessel_metadata: {
name: "kapla",
mmsi: "123456789",
client_id: "vessels.urn:mrn:signalk:uuid:5b4f7543-7153-4840-b139-761310b242fd",
length: "12",
beam: "10",
height: "24",
ship_type: "36",
plugin_version: "0.0.1",
signalk_version: "signalk_version",
time: moment.utc().subtract(69, 'minutes').format()
/* To trigger monitor_offline quickly */
},
vessel_metrics: metrics_kapla,
user_tables: [
{ url: '/stays', res_body_length: 3},
// not processed yet, { url: '/moorages', res_body_length: 2},
{ url: '/logbook', res_body_length: 2},
{ url: '/metadata', res_body_length: 1}
],
user_views: [
// not processed yet, { url: '/stays_view', res_body_length: 1},
// not processed yet, { url: '/moorages_view', res_body_length: 1},
{ url: '/logs_view', res_body_length: 0},
{ url: '/log_view', res_body_length: 2},
//{ url: '/stats_view', res_body_length: 1},
{ url: '/vessels_view', res_body_length: 1},
],
user_patchs: [
{ url: '/logbook?id=eq.1',
patch: {
name: "patch log name",
notes: "new log note"
},
},
{ url: '/stays?id=eq.1',
patch: {
name: "patch stay name",
stay_code: 2,
notes: "new stay note"
},
},
/* not processed yet, { url: '/moorages?id=eq.1',
patch: {
name: "patch moorage name",
home_flag: true,
stay_code: 2,
notes: "new moorage note"
},
}
*/
],
user_fn: [
{ url: '/rpc/timelapse_fn',
payload: {
start_log: 1
},
res: {
obj_name: 'geojson'
}
},
{ url: '/rpc/export_logbook_geojson_fn',
payload: {
_id: 1
},
res: {
obj_name: 'geojson'
}
},
{ url: '/rpc/export_logbook_gpx_fn',
payload: {
_id: 1
},
res: {
obj_name: null
}
},
{ url: '/rpc/vessel_fn',
payload: null,
res: {
obj_name: 'vessel'
}
},
{ url: '/rpc/settings_fn',
payload: null,
res: {
obj_name: 'settings'
}
},
{ url: '/rpc/versions_fn',
payload: null,
res: {
obj_name: 'versions'
}
}
],
otp_fn: [
{ url: '/rpc/generate_otp_fn',
payload: { email: 'demo+kapla@openplotter.cloud' },
res: {
otp: 0
}
},
{ url: '/rpc/email_fn',
payload: { token: null },
res: {
obj_name: 'settings'
}
},
{ url: '/rpc/generate_otp_fn',
payload: { email: 'demo+kapla@openplotter.cloud' },
res: {
otp: 0
}
},
{ url: '/rpc/pushover_fn',
payload: { token: null, pushover_user_key: '1234567890azerty!'},
res: {
obj_name: 'settings'
}
},
{ url: '/rpc/generate_otp_fn',
payload: { email: 'demo+kapla@openplotter.cloud' },
res: {
otp: 0
}
},
{ url: '/rpc/telegram_fn',
payload: { token: null, telegram_obj: {"chat": {"id": 1234567890, "type": "private", "title": null, "all_members_are_administrators": null}, "date": "NOW", "from": {"id": 1234567890, "is_bot": false, "first_name": "Kapla", "language_code": "en"}} },
res: {
obj_name: 'settings'
}
}
]
},
{ cname: process.env.PGSAIL_API_URI, name: "PostgSail unit test, aava",
signin: { email: 'demo+aava@openplotter.cloud', pass: 'test', firstname:'first_aava', lastname:'last_aava'},
login: { email: 'demo+aava@openplotter.cloud', pass: 'test'},
vessel: { vessel_email: "demo+aava@openplotter.cloud", vessel_mmsi: "787654321", vessel_name: "aava"},
preferences: { key: '{email_notifications}', value: false }, /* Disable email_notifications */
vessel_metadata: {
name: "aava",
mmsi: "787654321",
client_id: "vessels.urn:mrn:imo:mmsi:787654321",
length: "12",
beam: "10",
height: "24",
ship_type: "37",
plugin_version: "1.0.2",
signalk_version: "1.20.0",
time: moment().subtract(69, 'minutes').format()
},
vessel_metrics: metrics_aava,
user_tables: [
{ url: '/stays', res_body_length: 2},
// not processed yet, { url: '/moorages', res_body_length: 2},
{ url: '/logbook', res_body_length: 1},
{ url: '/metadata', res_body_length: 1}
],
user_views: [
// not processed yet, { url: '/stays_view', res_body_length: 1},
// not processed yet, { url: '/moorages_view', res_body_length: 1},
{ url: '/logs_view', res_body_length: 0},
{ url: '/log_view', res_body_length: 1},
//{ url: '/stats_view', res_body_length: 1},
{ url: '/vessels_view', res_body_length: 1},
],
user_patchs: [
{ url: '/logbook?id=eq.3',
patch: {
name: "patch log name",
notes: "new log note"
},
},
{ url: '/stays?id=eq.4',
patch: {
name: "patch stay name",
stay_code: 2,
notes: "new stay note"
},
},
/* not processed yet, { url: '/moorages?id=eq.1',
patch: {
name: "patch moorage name",
home_flag: true,
stay_code: 2,
notes: "new moorage note"
},
}
*/
],
user_fn: [
{ url: '/rpc/timelapse_fn',
payload: {
start_log: 3
},
res: {
obj_name: 'geojson'
}
},
{ url: '/rpc/export_logbook_geojson_fn',
payload: {
_id: 3
},
res: {
obj_name: 'geojson'
}
},
{ url: '/rpc/export_logbook_gpx_fn',
payload: {
_id: 3
},
res: {
obj_name: null
}
},
{ url: '/rpc/vessel_fn',
payload: null,
res: {
obj_name: 'vessel'
}
},
{ url: '/rpc/settings_fn',
payload: null,
res: {
obj_name: 'settings'
}
}
],
otp_fn: [
{ url: '/rpc/generate_otp_fn',
payload: { email: 'demo+aava@openplotter.cloud' },
res: {
otp: 0
}
},
{ url: '/rpc/email_fn',
payload: { token: null },
res: {
obj_name: 'settings'
}
},
{ url: '/rpc/generate_otp_fn',
payload: { email: 'demo+aava@openplotter.cloud' },
res: {
otp: 0
}
},
{ url: '/rpc/pushover_fn',
payload: { token: null, pushover_user_key: '0987654321qwerty!'},
res: {
obj_name: 'settings'
}
},
{ url: '/rpc/generate_otp_fn',
payload: { email: 'demo+aava@openplotter.cloud' },
res: {
otp: 0
}
},
{ url: '/rpc/telegram_fn',
payload: { token: null, telegram_obj: {"chat": {"id": 9876543210, "type": "private", "title": null, "all_members_are_administrators": null}, "date": "NOW", "from": {"id": 9876543210, "is_bot": false, "first_name": "Aava", "language_code": "en"}} },
res: {
obj_name: 'settings'
}
},
]
}
].forEach( function(test){
//console.log(`${test.cname}`);
describe(`${test.name}`, function(){
request = supertest.agent(test.cname);
request.set('User-Agent', 'PostgSail unit tests');
describe("OpenAPI description", function(){
it('/', function(done) {
request = supertest.agent(test.cname);
request
.get('/')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
should.exist(res.body.paths['/rpc/signup']);
should.exist(res.body.paths['/rpc/login']);
should.exist(res.body.paths['/rpc/reset']);
should.exist(res.body.paths['/rpc/recover']);
//should.exist(res.body.paths['/rpc/generate_otp_fn']);
should.exist(res.body.paths['/rpc/pushover_fn']);
should.exist(res.body.paths['/rpc/telegram_fn']);
should.exist(res.body.paths['/rpc/telegram']);
done(err);
});
});
}); // OpenAPI description
describe("Get JWT user_role", function(){
it('/rpc/signup return user_role jwt token', function(done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post('/rpc/signup')
.send(test.signin)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
should.exist(res.body.token);
user_jwt = res.body.token;
should.exist(user_jwt);
done(err);
});
});
it('/rpc/login return user_role jwt token', function(done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post('/rpc/login')
.send(test.login)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
should.exist(res.body.token);
//res.body.token.should.match(user_jwt);
console.log(user_jwt);
should.exist(user_jwt);
done(err);
});
});
}); // JWT user_role
describe("OpenAPI with JWT user_role", function(){
it('/', function(done) {
request = supertest.agent(test.cname);
request
.get('/')
.set('Authorization', `Bearer ${user_jwt}`)
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
// Functions
should.exist(res.body.paths['/rpc/register_vessel']);
should.exist(res.body.paths['/rpc/update_user_preferences_fn']);
should.exist(res.body.paths['/rpc/settings_fn']);
should.exist(res.body.paths['/rpc/versions_fn']);
// Tables
should.exist(res.body.paths['/metadata']);
should.exist(res.body.paths['/metrics']);
should.exist(res.body.paths['/logbook']);
should.exist(res.body.paths['/stays']);
should.exist(res.body.paths['/moorages']);
// Views
should.exist(res.body.paths['/logs_view']);
should.exist(res.body.paths['/moorages_view']);
should.exist(res.body.paths['/stays_view']);
should.exist(res.body.paths['/vessels_view']);
//should.exist(res.body.paths['/stats_view']);
should.exist(res.body.paths['/monitoring_view']);
done(err);
});
});
}); // OpenAPI JWT user_role
describe("Set preferences email_notifications, JWT user_role", function(){
it('/rpc/update_user_preferences_fn return true', function(done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post('/rpc/update_user_preferences_fn')
.send(test.preferences)
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
//console.log(res.text);
should.exist(res.text);
res.text.should.match('true');
done(err);
});
});
}); // JWT user_role
describe("Get versions, JWT user_role", function(){
it('/rpc/versions_fn return json', function(done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.get('/rpc/versions_fn')
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
//console.log(res.text);
should.exist(res.body.api_version);
should.exist(res.body.sys_version);
done(err);
});
});
}); // JWT user_role
describe("Get JWT vessel_role from user_role", function(){
it('/rpc/register_vessel return vessel_role jwt token', function(done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post('/rpc/register_vessel')
.send(test.vessel)
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
should.exist(res.body.token);
vessel_jwt = res.body.token;
console.log(vessel_jwt);
should.exist(vessel_jwt);
// Save vessel JWT token for later use.
fs.writeFile(`vessel_jwt_${test.vessel.vessel_name}.txt`, vessel_jwt, (err) => {
// In case of a error throw err.
if (err) throw err;
})
done(err);
});
});
}); // JWT user_role
describe("OpenAPI with JWT vessel_role", function(){
it('/', function(done) {
request = supertest.agent(test.cname);
request
.get('/')
.set('Authorization', `Bearer ${vessel_jwt}`)
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
should.exist(res.body.paths['/metadata']);
should.exist(res.body.paths['/metrics']);
should.exist(res.body.paths['/logbook']);
should.exist(res.body.paths['/stays']);
should.exist(res.body.paths['/moorages']);
done(err);
});
});
}); // OpenAPI JWT vessel_role
describe("Get vessel details view, JWT user_role", function(){
it('/vessels_view return json', function(done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.get('/vessels_view')
.set('Authorization', `Bearer ${user_jwt}`)
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
console.log(res.body);
//res.body.length.should.match(0);
res.body.length.should.match(1);
//res.body[0].last_contact.should.match('Never');
should.equal(res.body[0].last_contact, null);
done(err);
});
});
}); // JWT user_role
describe("Get vessel details function, JWT user_role", function(){
// no metadata from vessel so error - unrecognized configuration parameter "vessel.client_id"
it('/rpc/vessel_fn return json', function(done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.get('/rpc/vessel_fn')
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.end(function(err,res){
console.log(res.body)
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
should.exist(res.body);
//body = res.body;
//console.log(res.text);
done(err);
});
});
}); // JWT user_role
describe("Vessel POST metadata, JWT vessel_role", function(){
it('/metadata?on_conflict=vessel_id', function(done) {
request = supertest.agent(test.cname);
request
.post('/metadata?on_conflict=vessel_id')
.send(test.vessel_metadata)
.set('Authorization', `Bearer ${vessel_jwt}`)
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.set('Prefer', 'return=headers-only,resolution=merge-duplicates')
.end(function(err,res){
res.status.should.equal(201);
//console.log(res.header);
should.exist(res.header['server']);
res.header['server'].should.match(new RegExp('postgrest','g'));
done(err);
});
});
}); // Vessel metadata JWT vessel_role
describe("Vessel POST metrics, JWT vessel_role", function(){
let data = [];
//console.log(vessel_metrics['metrics'][0]);
let i;
for (i = 0; i < test.vessel_metrics['metrics'].length; i++) {
data[i] = test.vessel_metrics['metrics'][i];
// Override time, -2h to allow to new data later without delay.
data[i]['time'] = moment.utc().subtract(2, 'hours').add(i, 'minutes').format();
// Override client_id
data[i]['client_id'] = test.vessel_metadata.client_id;
}
// Force last entry to be back in time from previous, it should be ignore silently
data.at(-1).time = moment.utc(data.at(-2).time).subtract(1, 'minutes').format();
//console.log(data[0]);
it('/metrics?select=time', function(done) {
request = supertest.agent(test.cname);
request
.post('/metrics?select=time')
.send(data)
.set('Authorization', `Bearer ${vessel_jwt}`)
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.set('Prefer', 'return=representation')
.end(function(err,res){
//console.log(res.body);
res.status.should.equal(201);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
should.exist(res.body);
res.body.length.should.match(test.vessel_metrics['metrics'].length-1);
done(err);
});
});
}); // Vessel POST metrics JWT vessel_role
/*
describe("run_cron_jobs() JWT vessel_role", function(){
it('/rpc/run_cron_jobs', function(done) {
request = supertest.agent(test.cname);
request
.get('/rpc/run_cron_jobs')
.set('Authorization', `Bearer ${vessel_jwt}`)
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
done();
});
});
}); // run_cron_jobs() JWT vessel_role
*/
describe("Table endpoint, JWT user_role", function(){
test.user_tables.forEach(function (subtest) {
it(`${subtest.url}`, function(done) {
try {
//console.log(`${subtest.url} ${subtest.res_body_length}`);
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.get(`${subtest.url}`)
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
should.exist(res.body);
res.body.length.should.match(subtest.res_body_length);
done(err);
});
}
catch (error) {
done();
}
});
});
}); // Table endpoint
describe("Views endpoint, JWT user_role", function(){
test.user_views.forEach(function (subtest) {
it(`${subtest.url}`, function(done) {
try {
//console.log(`${subtest.url} ${subtest.res_body_length}`);
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.get(`${subtest.url}`)
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
should.exist(res.body);
res.body.length.should.match(subtest.res_body_length);
done(err);
});
}
catch (error) {
done();
}
});
});
}); // Views endpoint
describe("Patch endpoint, JWT user_role", function(){
test.user_patchs.forEach(function (subtest) {
it(`${subtest.url}`, function(done) {
try {
//console.log(`${subtest.url} ${subtest.res_body_length}`);
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.patch(subtest.url)
.send(subtest.patch)
.set('Content-Type', 'application/json')
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(204);
should.exist(res.header['server']);
res.header['server'].should.match(new RegExp('postgrest','g'));
done(err);
});
}
catch (error) {
done();
}
});
});
}); // Patch endpoint
describe("Function endpoint, JWT user_role", function(){
test.user_fn.forEach(function (subtest) {
it(`${subtest.url}`, function(done) {
try {
//console.log(`${subtest.url} ${subtest.res_body_length}`);
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post(subtest.url)
.send(subtest.payload)
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
//should.exist(res.body);
done(err);
});
}
catch (error) {
done();
}
});
});
}); // Function endpoint
/*
describe("Function OTP endpoint, JWT user_role", function(){
let otp = null;
test.otp_fn.forEach(function (subtest) {
otp = null;
it(`${subtest.url}`, function(done) {
try {
//console.log(`${subtest.url} ${subtest.res}`);
if (otp) {
subtest.payload.token = otp;
}
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post(subtest.url)
.send(subtest.payload)
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
console.log(res.body);
should.exist(res.body);
if (subtest.url == '/rpc/generate_otp_fn') {
otp = res.body;
} else {
res.text.should.match('true');
otp = null;
}
done(err);
});
}
catch (error) {
done();
}
});
});
}); // Function OTP endpoint
*/
}); // OpenAPI description
}); // CNAMEs Array

645
tests/index2.js Normal file
View File

@@ -0,0 +1,645 @@
'use strict';
/*
* Unit test #2, additional aava sample vessel metrics
*
* process.env.PGSAIL_API_URI = from inside the docker
*
* npm install supertest should mocha mochawesome moment
* alias mocha="./node_modules/mocha/bin/_mocha"
* mocha index.js --reporter mochawesome --reporter-options reportDir=/mnt/postgsail/,reportFilename=report_api.html
*
*/
const supertest = require("supertest");
const should = require("should");
let request = null;
let user_jwt = null;
let vessel_jwt = null;
var moment = require('moment');
const metrics_simulator = require('./metrics_sample_simulator.json');
// CNAMEs Array
[
{ cname: process.env.PGSAIL_API_URI, name: "PostgSail unit test 2, aava",
signin: { email: 'demo+aava@openplotter.cloud', pass: 'test', firstname:'first_aava', lastname:'last_aava'},
login: { email: 'demo+aava@openplotter.cloud', pass: 'test'},
vessel: { vessel_email: "demo+aava@openplotter.cloud", vessel_mmsi: "787654321", vessel_name: "aava"},
preferences: { key: '{email_notifications}', value: false }, /* Disable email_notifications */
vessel_metadata: {
name: "aava",
mmsi: "787654321",
client_id: "vessels.urn:mrn:imo:mmsi:787654321",
length: "12",
beam: "10",
height: "24",
ship_type: "37",
plugin_version: "1.0.2",
signalk_version: "1.20.0",
time: moment().subtract(69, 'minutes').format()
},
vessel_metrics: metrics_simulator,
user_tables: [
{ url: '/stays', res_body_length: 3},
// not processed yet, { url: '/moorages', res_body_length: 2},
{ url: '/logbook', res_body_length: 2},
{ url: '/metadata', res_body_length: 1}
],
user_views: [
// not processed yet, { url: '/stays_view', res_body_length: 1},
// not processed yet, { url: '/moorages_view', res_body_length: 1},
{ url: '/logs_view', res_body_length: 1},
{ url: '/log_view', res_body_length: 2},
//{ url: '/stats_view', res_body_length: 1},
{ url: '/vessels_view', res_body_length: 1},
],
user_patchs: [
{ url: '/logbook?id=eq.4',
patch: {
name: "patch log name 2",
notes: "new log note 2"
},
},
{ url: '/stays?id=eq.5',
patch: {
name: "patch stay name 2",
stay_code: 2,
notes: "new stay note 2"
},
},
/* not processed yet, { url: '/moorages?id=eq.1',
patch: {
name: "patch moorage name",
home_flag: true,
stay_code: 2,
notes: "new moorage note"
},
}
*/
],
user_fn: [
{ url: '/rpc/timelapse_fn',
payload: {
start_log: 4
},
res: {
obj_name: 'geojson'
}
},
{ url: '/rpc/export_logbook_geojson_fn',
payload: {
_id: 4
},
res: {
obj_name: 'geojson'
}
},
{ url: '/rpc/export_logbook_gpx_fn',
payload: {
_id: 4
},
res: {
obj_name: null
}
},
{ url: '/rpc/vessel_fn',
payload: null,
res: {
obj_name: 'vessel'
}
},
{ url: '/rpc/settings_fn',
payload: null,
res: {
obj_name: 'settings'
}
}
],
others_fn: [
{ url: '/rpc/generate_otp_fn',
payload: { email: 'demo+aava@openplotter.cloud' },
res: {
obj_name: 'settings'
}
},
{ url: '/rpc/pushover_fn',
// invalid key to avoid trigger notification
payload: { token: 'zxy', pushover_test_key: '987azerty#'},
res: {
obj_name: 'settings'
}
},
{ url: '/rpc/update_user_preferences_fn',
//payload: { key: '{xyz}', value: '987azerty#'},
// invalid key to avoid trigger notification
payload: { key: '{telegram_test}', value: '{"id": 987654321, "is_bot": false, "first_name": "aaVa", "language_code": "en"}' },
res: {
obj_name: 'settings'
}
},
{ url: '/rpc/bot',
payload: { email: 'demo+aava@openplotter.cloud', chat_id: 987654321},
res: {
obj_name: 'settings'
}
}
]
}
].forEach( function(test){
//console.log(`${test.cname}`);
describe(`${test.name}`, function(){
request = supertest.agent(test.cname);
request.set('User-Agent', 'PostgSail unit tests');
describe("OpenAPI description", function(){
it('/', function(done) {
request = supertest.agent(test.cname);
request
.get('/')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
should.exist(res.body.paths['/rpc/signup']);
should.exist(res.body.paths['/rpc/login']);
//should.exist(res.body.paths['/rpc/generate_otp_fn']);
should.exist(res.body.paths['/rpc/pushover_fn']);
should.exist(res.body.paths['/rpc/telegram_fn']);
//should.exist(res.body.paths['/rpc/bot']);
done(err);
});
});
}); // OpenAPI description
describe("Get JWT user_role", function(){
it('/rpc/signup return user_role jwt token', function(done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post('/rpc/signup')
.send(test.signin)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
should.exist(res.body.token);
user_jwt = res.body.token;
should.exist(user_jwt);
done(err);
});
});
it('/rpc/login return user_role jwt token', function(done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post('/rpc/login')
.send(test.login)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
should.exist(res.body.token);
//res.body.token.should.match(user_jwt);
console.log(user_jwt);
should.exist(user_jwt);
done(err);
});
});
}); // JWT user_role
describe("OpenAPI with JWT user_role", function(){
it('/', function(done) {
request = supertest.agent(test.cname);
request
.get('/')
.set('Authorization', `Bearer ${user_jwt}`)
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
// Function
should.exist(res.body.paths['/rpc/register_vessel']);
should.exist(res.body.paths['/rpc/update_user_preferences_fn']);
should.exist(res.body.paths['/rpc/settings_fn']);
should.exist(res.body.paths['/rpc/versions_fn']);
// Tables
should.exist(res.body.paths['/metadata']);
should.exist(res.body.paths['/metrics']);
should.exist(res.body.paths['/logbook']);
should.exist(res.body.paths['/stays']);
should.exist(res.body.paths['/moorages']);
// Views
should.exist(res.body.paths['/logs_view']);
should.exist(res.body.paths['/moorages_view']);
should.exist(res.body.paths['/stays_view']);
should.exist(res.body.paths['/vessels_view']);
// should.exist(res.body.paths['/stats_view']);
should.exist(res.body.paths['/monitoring_view']);
done(err);
});
});
}); // OpenAPI JWT user_role
describe("Set preferences email_notifications, JWT user_role", function(){
it('/rpc/update_user_preferences_fn return true', function(done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post('/rpc/update_user_preferences_fn')
.send(test.preferences)
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
//console.log(res.text);
should.exist(res.text);
res.text.should.match('true');
done(err);
});
});
}); // JWT user_role
describe("Get versions, JWT user_role", function(){
it('/rpc/versions_fn return json', function(done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.get('/rpc/versions_fn')
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
//console.log(res.text);
should.exist(res.body.api_version);
should.exist(res.body.sys_version);
done(err);
});
});
}); // JWT user_role
describe("Get JWT vessel_role from user_role", function(){
it('/rpc/register_vessel return vessel_role jwt token', function(done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post('/rpc/register_vessel')
.send(test.vessel)
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
should.exist(res.body.token);
vessel_jwt = res.body.token;
console.log(vessel_jwt);
should.exist(vessel_jwt);
done(err);
});
});
}); // JWT user_role
describe("OpenAPI with JWT vessel_role", function(){
it('/', function(done) {
request = supertest.agent(test.cname);
request
.get('/')
.set('Authorization', `Bearer ${vessel_jwt}`)
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
should.exist(res.body.paths['/metadata']);
should.exist(res.body.paths['/metrics']);
should.exist(res.body.paths['/logbook']);
should.exist(res.body.paths['/stays']);
should.exist(res.body.paths['/moorages']);
done(err);
});
});
}); // OpenAPI JWT vessel_role
describe("Get vessel details view, JWT user_role", function(){
it('/vessels_view return json', function(done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.get('/vessels_view')
.set('Authorization', `Bearer ${user_jwt}`)
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
console.log(res.body);
//res.body.length.should.match(0);
res.body.length.should.match(1);
//res.body[0].last_contact.should.match('Never');
should.exist(res.body[0].last_contact);
done(err);
});
});
}); // JWT user_role
describe("Get vessel details function, JWT user_role", function(){
it('/rpc/vessel_fn return json', function(done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post('/rpc/vessel_fn')
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
//should.exist(res.body);
//body = res.body;
console.log(res.text);
done(err);
});
});
}); // JWT user_role
describe("Vessel POST metadata, JWT vessel_role", function(){
it('/metadata', function(done) {
request = supertest.agent(test.cname);
request
.post('/metadata')
.send(test.vessel_metadata)
.set('Authorization', `Bearer ${vessel_jwt}`)
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.set('Prefer', 'return=headers-only')
.end(function(err,res){
res.status.should.equal(201);
//console.log(res.header);
should.exist(res.header['server']);
res.header['server'].should.match(new RegExp('postgrest','g'));
done(err);
});
});
}); // Vessel metadata JWT vessel_role
describe("Vessel POST metrics, JWT vessel_role", function(){
let data = [];
//console.log(vessel_metrics['metrics'][0]);
let i;
for (i = 0; i < test.vessel_metrics['metrics'].length; i++) {
data[i] = test.vessel_metrics['metrics'][i];
// Override time, +1h because previous sample include 47 entry.
data[i]['time'] = moment().add(1, 'hour').add(i, 'minutes').format();
// Override client_id
data[i]['client_id'] = test.vessel_metadata.client_id;
}
console.log(data[0]);
it('/metrics?select=time', function(done) {
request = supertest.agent(test.cname);
request
.post('/metrics?select=time')
.send(data)
.set('Authorization', `Bearer ${vessel_jwt}`)
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.set('Prefer', 'return=representation')
.end(function(err,res){
//console.log(res.body);
res.status.should.equal(201);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
should.exist(res.body);
res.body.length.should.match(test.vessel_metrics['metrics'].length);
done(err);
});
});
}); // Vessel POST metrics JWT vessel_role
/*
describe("run_cron_jobs() JWT vessel_role", function(){
it('/rpc/run_cron_jobs', function(done) {
request = supertest.agent(test.cname);
request
.get('/rpc/run_cron_jobs')
.set('Authorization', `Bearer ${vessel_jwt}`)
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
done();
});
});
}); // run_cron_jobs() JWT vessel_role
*/
describe("Table endpoint, JWT user_role", function(){
test.user_tables.forEach(function (subtest) {
it(`${subtest.url}`, function(done) {
try {
//console.log(`${subtest.url} ${subtest.res_body_length}`);
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.get(`${subtest.url}`)
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
should.exist(res.body);
res.body.length.should.match(subtest.res_body_length);
done(err);
});
}
catch (error) {
done();
}
});
});
}); // Table endpoint
describe("Views endpoint, JWT user_role", function(){
test.user_views.forEach(function (subtest) {
it(`${subtest.url}`, function(done) {
try {
//console.log(`${subtest.url} ${subtest.res_body_length}`);
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.get(`${subtest.url}`)
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
should.exist(res.body);
res.body.length.should.match(subtest.res_body_length);
done(err);
});
}
catch (error) {
done();
}
});
});
}); // Views endpoint
describe("Patch endpoint, JWT user_role", function(){
test.user_patchs.forEach(function (subtest) {
it(`${subtest.url}`, function(done) {
try {
//console.log(`${subtest.url} ${subtest.res_body_length}`);
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.patch(subtest.url)
.send(subtest.patch)
.set('Content-Type', 'application/json')
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(204);
should.exist(res.header['server']);
res.header['server'].should.match(new RegExp('postgrest','g'));
done(err);
});
}
catch (error) {
done();
}
});
});
}); // Patch endpoint
describe("Function user_fn endpoint, JWT user_role", function(){
test.user_fn.forEach(function (subtest) {
it(`${subtest.url}`, function(done) {
try {
//console.log(`${subtest.url} ${subtest.res_body_length}`);
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post(subtest.url)
.send(subtest.payload)
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
//should.exist(res.body);
done(err);
});
}
catch (error) {
done();
}
});
});
}); // Function endpoint
/*
describe("Function others endpoint, JWT user_role", function(){
let otp = null;
test.others_fn.forEach(function (subtest) {
it(`${subtest.url}`, function(done) {
try {
//console.log(`${subtest.url} ${subtest.res_body_length}`);
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post(subtest.url)
.send(subtest.payload)
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
//console.log(res.body);
should.exist(res.body);
if (subtest.url == '/rpc/generate_otp_fn') {
otp = res.body.text();
}
done(err);
});
}
catch (error) {
done();
}
});
});
}); // Function endpoint
*/
}); // OpenAPI description
}); // CNAMEs Array

836
tests/index3.js Normal file
View File

@@ -0,0 +1,836 @@
'use strict';
/*
* Unit test #3, check post cron results, moorages, stays and stats
*
* process.env.PGSAIL_API_URI = from inside the docker
*
* npm install supertest should mocha mochawesome moment
* alias mocha="./node_modules/mocha/bin/_mocha"
* mocha index.js --reporter mochawesome --reporter-options reportDir=/mnt/postgsail/,reportFilename=report_api.html
*
*/
const supertest = require("supertest");
// Deprecated
const should = require("should");
//const chai = require("chai");
//const should = chai.should();
let request = null;
let user_jwt = null;
let vessel_jwt = null;
var moment = require('moment');
// CNAMEs Array
[
{ cname: process.env.PGSAIL_API_URI, name: "PostgSail unit test kapla",
signin: { email: 'demo+kapla@openplotter.cloud', pass: 'test', firstname:'First_kapla', lastname:'Last_kapla'},
login: { email: 'demo+kapla@openplotter.cloud', pass: 'test'},
vessel: { vessel_email: "demo+kapla@openplotter.cloud", vessel_mmsi: null, vessel_name: "kapla"},
preferences: { key: '{email_notifications}', value: false }, /* Disable email_notifications */
vessel_metadata: {
name: "kapla",
mmsi: "123456789",
client_id: "vessels.urn:mrn:imo:mmsi:123456789",
length: "12",
beam: "10",
height: "24",
ship_type: "36",
plugin_version: "0.0.1",
signalk_version: "1.12.0",
time: moment().subtract(69, 'minutes').format()
/* To trigger monitor_offline quickly */
},
user_tables: [
{ url: '/stays', res_body_length: 3},
{ url: '/moorages', res_body_length: 3},
{ url: '/logbook', res_body_length: 2},
{ url: '/metadata', res_body_length: 1}
],
user_views: [
{ url: '/stays_view', res_body_length: 2},
{ url: '/moorages_view', res_body_length: 2},
{ url: '/logs_view', res_body_length: 2},
{ url: '/log_view', res_body_length: 2},
//{ url: '/stats_view', res_body_length: 1},
{ url: '/vessels_view', res_body_length: 1},
],
user_patchs: [
{ url: '/logbook?id=eq.1',
patch: {
name: "patch log name 3",
notes: "new log note 3"
},
},
{ url: '/stays?id=eq.1',
patch: {
name: "patch stay name 3",
stay_code: 2,
notes: "new stay note 3"
},
},
{ url: '/moorages?id=eq.1',
patch: {
name: "patch moorage name 3",
home_flag: true,
stay_code: 2,
notes: "new moorage note 3"
},
}
],
user_fn: [
{ url: '/rpc/timelapse_fn',
payload: {
start_log: 2
},
res: {
obj_name: 'geojson'
}
},
{ url: '/rpc/export_logbook_geojson_fn',
payload: {
_id: 2
},
res: {
obj_name: 'geojson'
}
},
{ url: '/rpc/export_logbook_gpx_fn',
payload: {
_id: 2
},
res: {
obj_name: null
}
},
{ url: '/rpc/export_logbook_kml_fn',
payload: {
_id: 2
},
res: {
obj_name: null
}
},
{ url: '/rpc/export_moorages_geojson_fn',
payload: {},
res: {
obj_name: 'geojson'
}
},
{ url: '/rpc/export_moorages_gpx_fn',
payload: {},
res: {
obj_name: null
}
},
{ url: '/rpc/find_log_from_moorage_fn',
payload: {
_id: 2
},
res: {
obj_name: 'geojson'
}
},
{ url: '/rpc/find_log_to_moorage_fn',
payload: {
_id: 2
},
res: {
obj_name: 'geojson'
}
},
{ url: '/rpc/vessel_fn',
payload: null,
res: {
obj_name: 'vessel'
}
},
{ url: '/rpc/settings_fn',
payload: null,
res: {
obj_name: 'settings'
}
},
{ url: '/rpc/versions_fn',
payload: null,
res: {
obj_name: 'versions'
}
},
{ url: '/rpc/stats_logs_fn',
payload: {},
res: {
obj_name: 'stats'
}
},
{ url: '/rpc/stats_logs_fn',
payload: {
start_date: '2022-01-01',
end_date: '2022-06-12'
},
res: {
obj_name: null
}
},
],
email_otp_fn: [
{ url: '/rpc/generate_otp_fn',
payload: { email: 'demo+kapla@openplotter.cloud' },
res: {
otp: 0
}
},
{ url: '/rpc/email_fn',
//payload: { token: 'abc', pushover_user_key: '123qwerty!'},
// invalid key to avoid trigger notification
payload: { token: '123456' },
res: {
obj_name: 'settings'
}
}
],
pushover_fn: [
{ url: '/rpc/generate_otp_fn',
payload: { email: 'demo+kapla@openplotter.cloud' },
res: {
otp: 0
}
},
{ url: '/rpc/pushover_fn',
//payload: { token: 'abc', pushover_user_key: '123qwerty!'},
// invalid key to avoid trigger notification
payload: { token: null, pushover_test_key: '123qwerty!'},
res: {
obj_name: 'settings'
}
}
],
telegram_fn: [
{ url: '/rpc/generate_otp_fn',
payload: { email: 'demo+kapla@openplotter.cloud' },
res: {
otp: 0
}
},
{ url: '/rpc/telegram_fn',
//payload: { key: '{abc}', value: {"a": "1", "b": 2, "c": true}},
// invalid key to avoid trigger notification
payload: { token: null, telegram_test: '{"id": 123456789, "is_bot": false, "first_name": "kaplA", "language_code": "en"}' },
res: {
obj_name: 'settings'
}
}
]
},
{ cname: process.env.PGSAIL_API_URI, name: "PostgSail unit test, aava",
signin: {email: 'demo+aava@openplotter.cloud', pass: 'test', firstname:'first_aava', lastname:'last_aava'},
login: {email: 'demo+aava@openplotter.cloud', pass: 'test'},
vessel: {vessel_email: "demo+aava@openplotter.cloud", vessel_mmsi: null, vessel_name: "aava"},
preferences: { key: '{email_notifications}', value: false }, /* Disable email_notifications */
vessel_metadata: {
name: "aava",
mmsi: "787654321",
client_id: "vessels.urn:mrn:imo:mmsi:787654321",
length: "12",
beam: "10",
height: "24",
ship_type: "37",
plugin_version: "1.0.2",
signalk_version: "1.20.0",
time: moment().subtract(69, 'minutes').format()
},
user_tables: [
{ url: '/stays', res_body_length: 3},
{ url: '/moorages', res_body_length: 4},
{ url: '/logbook', res_body_length: 2},
{ url: '/metadata', res_body_length: 1}
],
user_views: [
{ url: '/stays_view', res_body_length: 2},
{ url: '/moorages_view', res_body_length: 2},
{ url: '/logs_view', res_body_length: 2},
{ url: '/log_view', res_body_length: 2},
//{ url: '/stats_view', res_body_length: 1},
{ url: '/vessels_view', res_body_length: 1},
],
user_patchs: [
{ url: '/logbook?id=eq.4',
patch: {
name: "patch log name 4",
notes: "new log note 4"
},
},
{ url: '/stays?id=eq.4',
patch: {
name: "patch stay name 4",
stay_code: 2,
notes: "new stay note 4"
},
},
{ url: '/moorages?id=eq.4',
patch: {
name: "patch moorage name",
home_flag: true,
stay_code: 2,
notes: "new moorage note"
},
}
],
user_fn: [
{ url: '/rpc/timelapse_fn',
payload: {
start_log: 4
},
res: {
obj_name: 'geojson'
}
},
{ url: '/rpc/export_logbook_geojson_fn',
payload: {
_id: 4
},
res: {
obj_name: 'geojson'
}
},
{ url: '/rpc/export_logbook_gpx_fn',
payload: {
_id: 4
},
res: {
obj_name: null
}
},
{ url: '/rpc/export_logbook_kml_fn',
payload: {
_id: 4
},
res: {
obj_name: null
}
},
{ url: '/rpc/export_logbooks_gpx_fn',
payload: {
start_log: 3,
end_log: 4
},
res: {
obj_name: null
}
},
{ url: '/rpc/export_logbooks_kml_fn',
payload: {
start_log: 3,
end_log: 4
},
res: {
obj_name: null
}
},
{ url: '/rpc/export_moorages_geojson_fn',
payload: {},
res: {
geojson: { type: 'FeatureCollection', features: [ [Object], [Object] ] }
}
},
{ url: '/rpc/export_moorages_gpx_fn',
payload: {},
res: {
obj_name: null
}
},
{ url: '/rpc/find_log_from_moorage_fn',
payload: {
_id: 4
},
res: { geojson: { type: 'FeatureCollection', features: [ [Object] ] } }
},
{ url: '/rpc/find_log_to_moorage_fn',
payload: {
_id: 4
},
res: { geojson: { type: 'FeatureCollection', features: [ [Object] ] } }
},
{ url: '/rpc/vessel_fn',
payload: null,
res: {
vessel: {
beam: 10,
mmsi: 787654321,
name: 'aava',
height: 24,
length: 37,
alpha_2: null,
country: null,
geojson: { type: 'Feature', geometry: [Object], properties: [Object] },
ship_type: 'Pleasure Craft',
created_at: '2023-08-17T16:32:13',
last_contact: '2023-08-17T15:23:14'
}
}
},
{ url: '/rpc/settings_fn',
payload: null,
res: {
settings: {
email: 'demo+aava@openplotter.cloud',
first: 'first_aava',
last: 'last_aava',
preferences: { badges: [Object], email_notifications: false },
created_at: '2023-08-17T16:32:12.701788',
username: 'F Last_Aava',
has_vessel: true
}
}
},
{ url: '/rpc/stats_logs_fn',
payload: {},
res: { // Compare keys only
stats: {
count: 2,
max_speed: 7.1,
max_distance: 8.2365,
max_duration: '01:11:00',
max_speed_id: 3,
sum_duration: '01:54:00',
max_wind_speed: 44.2,
max_distance_id: 3,
max_wind_speed_id: 4
}
}
},
{ url: '/rpc/stats_logs_fn',
payload: {
start_date: '2022-01-01',
end_date: '2022-06-12'
},
res: { stats: null }
},
],
others_fn: [
{ url: '/rpc/generate_otp_fn',
payload: { email: 'demo+aava@openplotter.cloud' },
res: {
obj_name: 'settings'
}
},
{ url: '/rpc/pushover_fn',
// invalid key to avoid trigger notification
payload: { token: 'zxy', pushover_test_key: '987azerty#'},
res: {
obj_name: 'settings'
}
},
{ url: '/rpc/update_user_preferences_fn',
//payload: { key: '{xyz}', value: '987azerty#'},
// invalid key to avoid trigger notification
payload: { key: '{telegram_test}', value: '{"id": 987654321, "is_bot": false, "first_name": "aaVa", "language_code": "en"}' },
res: {
obj_name: 'settings'
}
},
{ url: '/rpc/bot',
payload: { email: 'demo+aava@openplotter.cloud', chat_id: 987654321},
res: {
obj_name: 'settings'
}
}
]
}
].forEach( function(test){
//console.log(`${test.cname}`);
describe(`${test.name}`, function(){
request = supertest.agent(test.cname);
request.set('User-Agent', 'PostgSail unit tests');
describe("OpenAPI description", function(){
it('/', function(done) {
request = supertest.agent(test.cname);
request
.get('/')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
should.exist(res.body.paths['/rpc/signup']);
should.exist(res.body.paths['/rpc/login']);
//should.exist(res.body.paths['/rpc/generate_otp_fn']);
should.exist(res.body.paths['/rpc/pushover_fn']);
should.exist(res.body.paths['/rpc/telegram_fn']);
//should.exist(res.body.paths['/rpc/bot']);
done(err);
});
});
}); // OpenAPI description
describe("Get JWT user_role", function(){
it('/rpc/signup return user_role jwt token', function(done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post('/rpc/signup')
.send(test.signin)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
should.exist(res.body.token);
user_jwt = res.body.token;
should.exist(user_jwt);
done(err);
});
});
it('/rpc/login return user_role jwt token', function(done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post('/rpc/login')
.send(test.login)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
should.exist(res.body.token);
//res.body.token.should.match(user_jwt);
//console.log(user_jwt);
should.exist(user_jwt);
done(err);
});
});
}); // JWT user_role
describe("OpenAPI with JWT user_role", function(){
it('/', function(done) {
request = supertest.agent(test.cname);
request
.get('/')
.set('Authorization', `Bearer ${user_jwt}`)
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
// Function
should.exist(res.body.paths['/rpc/register_vessel']);
should.exist(res.body.paths['/rpc/update_user_preferences_fn']);
should.exist(res.body.paths['/rpc/settings_fn']);
should.exist(res.body.paths['/rpc/versions_fn']);
// Tables
should.exist(res.body.paths['/metadata']);
should.exist(res.body.paths['/metrics']);
should.exist(res.body.paths['/logbook']);
should.exist(res.body.paths['/stays']);
should.exist(res.body.paths['/moorages']);
// Views
should.exist(res.body.paths['/logs_view']);
should.exist(res.body.paths['/moorages_view']);
should.exist(res.body.paths['/stays_view']);
should.exist(res.body.paths['/vessels_view']);
//should.exist(res.body.paths['/stats_view']);
should.exist(res.body.paths['/monitoring_view']);
done(err);
});
});
}); // OpenAPI JWT user_role
describe("Set preferences email_notifications, JWT user_role", function(){
it('/rpc/update_user_preferences_fn return true', function(done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post('/rpc/update_user_preferences_fn')
.send(test.preferences)
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
//console.log(res.text);
should.exist(res.text);
res.text.should.match('true');
done(err);
});
});
}); // JWT user_role
describe("Get versions, JWT user_role", function(){
it('/rpc/versions_fn return json', function(done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.get('/rpc/versions_fn')
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
//console.log(res.text);
should.exist(res.body.api_version);
should.exist(res.body.sys_version);
done(err);
});
});
}); // JWT user_role
describe("Get JWT vessel_role from user_role", function(){
it('/rpc/register_vessel return vessel_role jwt token', function(done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post('/rpc/register_vessel')
.send(test.vessel)
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
should.exist(res.body.token);
vessel_jwt = res.body.token;
console.log(vessel_jwt);
should.exist(vessel_jwt);
done(err);
});
});
}); // JWT user_role
describe("Get vessel details view, JWT user_role", function(){
it('/vessels_view return json', function(done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.get('/vessels_view')
.set('Authorization', `Bearer ${user_jwt}`)
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
console.log(res.body);
//res.body.length.should.match(0);
res.body.length.should.match(1);
//res.body[0].last_contact.should.match('Never');
should.exist(res.body[0].last_contact);
done(err);
});
});
}); // JWT user_role
describe("Get vessel details function, JWT user_role", function(){
it('/rpc/vessel_fn return json', function(done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post('/rpc/vessel_fn')
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
//should.exist(res.body);
//body = res.body;
console.log(res.text);
done(err);
});
});
}); // JWT user_role
describe("Table endpoint, JWT user_role", function(){
test.user_tables.forEach(function (subtest) {
it(`${subtest.url}`, function(done) {
try {
//console.log(`${subtest.url} ${subtest.res_body_length}`);
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.get(`${subtest.url}`)
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.end(function(err,res){
console.log(res.body);
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
should.exist(res.body);
res.body.length.should.match(subtest.res_body_length);
done(err);
});
}
catch (error) {
done();
}
});
});
}); // Table endpoint
describe("Views endpoint, JWT user_role", function(){
test.user_views.forEach(function (subtest) {
it(`${subtest.url}`, function(done) {
try {
//console.log(`${subtest.url} ${subtest.res_body_length}`);
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.get(`${subtest.url}`)
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.end(function(err,res){
//console.log(res.body);
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
should.exist(res.body);
res.body.length.should.match(subtest.res_body_length);
console.log(res.body);
done(err);
});
}
catch (error) {
done();
}
});
});
}); // Views endpoint
describe("Patch endpoint, JWT user_role", function(){
test.user_patchs.forEach(function (subtest) {
it(`${subtest.url}`, function(done) {
try {
//console.log(`${subtest.url} ${subtest.res_body_length}`);
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.patch(subtest.url)
.send(subtest.patch)
.set('Content-Type', 'application/json')
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(204);
should.exist(res.header['server']);
res.header['server'].should.match(new RegExp('postgrest','g'));
console.log(res.body);
done(err);
});
}
catch (error) {
done();
}
});
});
}); // Patch endpoint
describe("Function user_fn endpoint, JWT user_role", function(){
test.user_fn.forEach(function (subtest) {
it(`${subtest.url}`, function(done) {
try {
//console.log(`${subtest.url} ${subtest.res_body_length}`);
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post(subtest.url)
.send(subtest.payload)
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
//should.exist(res.body);
console.log(res.body);
done(err);
});
}
catch (error) {
done();
}
});
});
}); // Function endpoint
/*
describe("Function others endpoint, JWT user_role", function(){
let otp = null;
test.others_fn.forEach(function (subtest) {
it(`${subtest.url}`, function(done) {
try {
//console.log(`${subtest.url} ${subtest.res_body_length}`);
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post(subtest.url)
.send(subtest.payload)
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
//console.log(res.body);
should.exist(res.body);
if (subtest.url == '/rpc/generate_otp_fn') {
otp = res.body.text();
}
done(err);
});
}
catch (error) {
done();
}
});
});
}); // Function endpoint
*/
}); // OpenAPI description
}); // CNAMEs Array

727
tests/index4.js Normal file
View File

@@ -0,0 +1,727 @@
"use strict";
/*
* Unit test #4
* OTP for email, Pushover, Telegram
*
* process.env.PGSAIL_API_URI = from inside the docker
*
* npm install supertest should mocha mochawesome moment
* alias mocha="./node_modules/mocha/bin/_mocha"
* mocha index4.js --reporter mochawesome --reporter-options reportDir=/mnt/postgsail/,reportFilename=report_api.html
*
*/
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const supertest = require("supertest");
// Deprecated
const should = require("should");
//const chai = require("chai");
//const should = chai.should();
let request = null;
let user_jwt = null;
var moment = require("moment");
// Users Array
[
{
cname: process.env.PGSAIL_API_URI,
name: "PostgSail unit test kapla",
signin: {
email: "demo+kapla@openplotter.cloud",
pass: "test",
firstname: "First_kapla",
lastname: "Last_kapla",
},
login: { email: "demo+kapla@openplotter.cloud", pass: "test" },
preferences: { key: "{email_valid}", value: false },
email_otp: [
{
url: "/rpc/generate_otp_fn",
payload: { email: "demo+kapla@openplotter.cloud" },
res: {
otp: 0,
},
},
{
url: "/rpc/email_fn",
payload: { token: null },
res: {
obj_name: "settings",
},
},
],
pushover_otp: [
{
//url: '/rpc/generate_otp_fn',
url: "/rpc/pushover_subscribe_link_fn",
//payload: { email: 'demo+kapla@openplotter.cloud' },
res: {
obj_name: "pushover_link",
},
},
{
url: "/rpc/pushover_fn",
payload: { token: null, pushover_user_key: "1234567890azerty!" },
res: {
obj_name: "settings",
},
},
],
telegram_otp: [
{
url: "/rpc/update_user_preferences_fn",
payload: { key: "{email_notifications}", value: false },
},
{
url: "/rpc/update_user_preferences_fn",
payload: { key: "{phone_notifications}", value: false },
},
{
//url: '/rpc/generate_otp_fn',
url: "/rpc/telegram_otp_fn",
payload: { email: "demo+kapla@openplotter.cloud" },
res: {
otp: 0,
},
},
{
url: "/rpc/telegram_fn",
payload: {
token: null,
telegram_obj: {
chat: {
id: 1234567890,
type: "private",
title: null,
all_members_are_administrators: null,
},
date: "NOW",
from: {
id: 1234567890,
is_bot: false,
first_name: "Kapla",
language_code: "en",
},
},
},
res: {},
},
],
telegram: { payload: { user_id: 1234567890 } },
telegram_fn: [{ url: "/rpc/vessel_fn" }, { url: "/monitoring_view" }],
badges: {
url: "/rpc/settings_fn",
payload: null,
res: {
obj_name: "settings",
},
},
monitoring: [
{
url: "/monitoring_view",
payload: null,
res: {},
},
{
url: "/monitoring_view2",
payload: null,
res: {},
},
{
url: "/monitoring_view3",
payload: null,
res: {},
},
{
url: "/monitoring_voltage",
payload: null,
res: {},
},
{
url: "/monitoring_temperatures",
payload: null,
res: {},
},
{
url: "/monitoring_humidity",
payload: null,
res: {},
},
],
eventlogs: {
url: "/eventlogs_view",
payload: null,
res: {},
},
public: [
{
url: "/rpc/update_user_preferences_fn",
payload: { key: "{public_logs}", value: true },
},
{
url: "/rpc/update_user_preferences_fn",
payload: { key: "{public_monitoring}", value: true },
},
],
},
{
cname: process.env.PGSAIL_API_URI,
name: "PostgSail unit test, aava",
signin: {
email: "demo+aava@openplotter.cloud",
pass: "test",
firstname: "first_aava",
lastname: "last_aava",
},
login: { email: "demo+aava@openplotter.cloud", pass: "test" },
preferences: { key: "{email_valid}", value: false },
email_otp: [
{
url: "/rpc/generate_otp_fn",
payload: { email: "demo+aava@openplotter.cloud" },
res: {
otp: 0,
},
},
{
url: "/rpc/email_fn",
payload: { token: null },
res: {
obj_name: "settings",
},
},
],
pushover_otp: [
{
//url: '/rpc/generate_otp_fn',
url: "/rpc/pushover_subscribe_link_fn",
//payload: { email: 'demo+aava@openplotter.cloud' },
res: {
obj_name: "pushover_link",
},
},
{
url: "/rpc/pushover_fn",
payload: { token: null, pushover_user_key: "0987654321qwerty!" },
res: {
obj_name: "settings",
},
},
],
telegram_otp: [
{
url: "/rpc/update_user_preferences_fn",
payload: { key: "{email_notifications}", value: false },
},
{
url: "/rpc/update_user_preferences_fn",
payload: { key: "{phone_notifications}", value: false },
},
{
//url: '/rpc/generate_otp_fn',
url: "/rpc/telegram_otp_fn",
payload: { email: "demo+aava@openplotter.cloud" },
res: {
otp: 0,
},
},
{
url: "/rpc/telegram_fn",
payload: {
token: null,
telegram_obj: {
chat: {
id: 9876543210,
type: "private",
title: null,
all_members_are_administrators: null,
},
date: "NOW",
from: {
id: 9876543210,
is_bot: false,
first_name: "Aava",
language_code: "en",
},
},
},
res: {},
},
],
telegram: { payload: { user_id: 9876543210 } },
telegram_fn: [{ url: "/rpc/vessel_fn" }, { url: "/monitoring_view" }],
badges: {
url: "/rpc/settings_fn",
payload: null,
res: {
obj_name: "settings",
},
},
monitoring: [
{
url: "/monitoring_view",
payload: null,
res: {},
},
{
url: "/monitoring_view2",
payload: null,
res: {},
},
{
url: "/monitoring_view3",
payload: null,
res: {},
},
{
url: "/monitoring_voltage",
payload: null,
res: {},
},
{
url: "/monitoring_temperatures",
payload: null,
res: {},
},
{
url: "/monitoring_humidity",
payload: null,
res: {},
},
],
eventlogs: {
url: "/eventlogs_view",
payload: null,
res: {},
},
public: [
{
url: "/rpc/update_user_preferences_fn",
payload: { key: "{public_logs}", value: true },
},
{
url: "/rpc/update_user_preferences_fn",
payload: { key: "{public_monitoring}", value: true },
},
],
},
].forEach(function (test) {
//console.log(`${test.cname}`);
describe(`${test.name}`, function () {
request = supertest.agent(test.cname);
request.set("User-Agent", "PostgSail unit tests");
describe("Get JWT user_role", function () {
it("/rpc/signup return user_role jwt token", function (done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post("/rpc/signup")
.send(test.signin)
.set("Accept", "application/json")
.end(function (err, res) {
res.status.should.equal(200);
should.exist(res.header["content-type"]);
should.exist(res.header["server"]);
res.header["content-type"].should.match(new RegExp("json", "g"));
res.header["server"].should.match(new RegExp("postgrest", "g"));
should.exist(res.body.token);
user_jwt = res.body.token;
should.exist(user_jwt);
done(err);
});
});
it("/rpc/login return user_role jwt token", function (done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post("/rpc/login")
.send(test.login)
.set("Accept", "application/json")
.end(function (err, res) {
res.status.should.equal(200);
should.exist(res.header["content-type"]);
should.exist(res.header["server"]);
res.header["content-type"].should.match(new RegExp("json", "g"));
res.header["server"].should.match(new RegExp("postgrest", "g"));
should.exist(res.body.token);
res.body.token.should.match(user_jwt);
console.log(user_jwt);
should.exist(user_jwt);
done(err);
});
});
}); // JWT user_role
describe("Set preferences email_notifications, JWT user_role", function () {
it("/rpc/update_user_preferences_fn return true", function (done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post("/rpc/update_user_preferences_fn")
.send(test.preferences)
.set("Authorization", `Bearer ${user_jwt}`)
.set("Accept", "application/json")
.set("Content-Type", "application/json")
.end(function (err, res) {
res.status.should.equal(200);
should.exist(res.header["content-type"]);
should.exist(res.header["server"]);
res.header["content-type"].should.match(new RegExp("json", "g"));
res.header["server"].should.match(new RegExp("postgrest", "g"));
//console.log(res.text);
should.exist(res.text);
res.text.should.match("true");
done(err);
});
});
}); // JWT user_role
describe("Function email OTP endpoint, JWT user_role", function () {
let otp = null;
test.email_otp.forEach(function (subtest) {
it(`${subtest.url}`, function (done) {
try {
//console.log(`${subtest.url} ${subtest.payload}`);
if (otp) {
subtest.payload.token = otp;
}
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post(subtest.url)
.send(subtest.payload)
.set("Authorization", `Bearer ${user_jwt}`)
.set("Accept", "application/json")
.end(function (err, res) {
res.status.should.equal(200);
should.exist(res.header["content-type"]);
should.exist(res.header["server"]);
res.header["content-type"].should.match(
new RegExp("json", "g")
);
res.header["server"].should.match(new RegExp("postgrest", "g"));
console.log(res.body);
should.exist(res.body);
if (subtest.url == "/rpc/generate_otp_fn") {
otp = res.body;
} else {
res.text.should.match("true");
}
done(err);
});
} catch (error) {
done();
}
});
});
}); // email OTP endpoint
describe("Function Pushover OTP endpoint, JWT user_role", function () {
let otp = null;
test.pushover_otp.forEach(function (subtest) {
it(`${subtest.url}`, function (done) {
try {
//console.log(`${subtest.url} ${subtest.payload}`);
if (otp) {
subtest.payload.token = otp;
}
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post(subtest.url)
.send(subtest.payload)
.set("Authorization", `Bearer ${user_jwt}`)
.set("Accept", "application/json")
.end(function (err, res) {
res.status.should.equal(200);
should.exist(res.header["content-type"]);
should.exist(res.header["server"]);
res.header["content-type"].should.match(
new RegExp("json", "g")
);
res.header["server"].should.match(new RegExp("postgrest", "g"));
//console.log(res.body);
should.exist(res.body);
if (subtest.url == "/rpc/pushover_subscribe_link_fn") {
should.exist(res.body.pushover_link.link);
let rx = /3D(\d+)\&/g;
//console.log(rx.exec(res.body.pushover_link.link)[1]);
let arr = rx.exec(res.body.pushover_link.link);
//console.log(arr);
console.log(arr[1]);
otp = arr[1];
} else {
res.text.should.match("true");
}
done(err);
});
} catch (error) {
done();
}
});
});
}); // pushover OTP endpoint
describe("Function Telegram OTP endpoint, JWT user_role", function () {
let otp = null;
test.telegram_otp.forEach(function (subtest) {
it(`${subtest.url}`, function (done) {
try {
console.log(`${subtest.url} ${subtest.payload.email} ${otp}`);
if (otp) {
subtest.payload.token = otp;
console.log(subtest.payload.telegram_obj.date);
subtest.payload.telegram_obj.date = moment.utc().format();
}
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post(subtest.url)
.send(subtest.payload)
.set("Authorization", `Bearer ${user_jwt}`)
.set("Accept", "application/json")
.end(function (err, res) {
res.status.should.equal(200);
should.exist(res.header["content-type"]);
should.exist(res.header["server"]);
res.header["content-type"].should.match(
new RegExp("json", "g")
);
res.header["server"].should.match(new RegExp("postgrest", "g"));
console.log(res.body);
should.exist(res.body);
if (subtest.url == "/rpc/telegram_otp_fn") {
console.log(res.body.otp_code);
otp = res.body.otp_code;
} else {
console.log(res.text);
res.text.should.match("true");
otp = null;
}
done(err);
});
} catch (error) {
done();
}
});
});
}); // telegram OTP endpoint
describe("telegram session, anonymous", function () {
it("/rpc/telegram return jwt token", function (done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post("/rpc/telegram")
.send(test.telegram.payload)
.set("Accept", "application/json")
.set("Content-Type", "application/json")
.end(function (err, res) {
res.status.should.equal(200);
should.exist(res.header["content-type"]);
should.exist(res.header["server"]);
res.header["content-type"].should.match(new RegExp("json", "g"));
res.header["server"].should.match(new RegExp("postgrest", "g"));
should.exist(res.body.token);
user_jwt = res.body.token;
console.log(res.body.token);
done(err);
});
});
}); // anonymous JWT
describe("Telegram endpoint, JWT user_role", function () {
let otp = null;
test.telegram_fn.forEach(function (subtest) {
it(`${subtest.url}`, function (done) {
try {
//console.log(`${subtest.url} ${subtest.res_body_length}`);
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.get(subtest.url)
.set("Authorization", `Bearer ${user_jwt}`)
.set("Accept", "application/json")
.end(function (err, res) {
res.status.should.equal(200);
should.exist(res.header["content-type"]);
should.exist(res.header["server"]);
res.header["content-type"].should.match(
new RegExp("json", "g")
);
res.header["server"].should.match(new RegExp("postgrest", "g"));
console.log(res.body);
should.exist(res.body);
done(err);
});
} catch (error) {
done();
}
});
});
}); // Function endpoint
/*
describe("Function others endpoint, JWT user_role", function(){
let otp = null;
test.others_fn.forEach(function (subtest) {
it(`${subtest.url}`, function(done) {
try {
//console.log(`${subtest.url} ${subtest.res_body_length}`);
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post(subtest.url)
.send(subtest.payload)
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
//console.log(res.body);
should.exist(res.body);
if (subtest.url == '/rpc/generate_otp_fn') {
otp = res.body.text();
}
done(err);
});
}
catch (error) {
done();
}
});
});
}); // Function endpoint
*/
describe("Badges, user jwt", function () {
it("/rpc/settings_fn return user settings", function (done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post("/rpc/settings_fn")
.set("Authorization", `Bearer ${user_jwt}`)
.set("Accept", "application/json")
.end(function (err, res) {
res.status.should.equal(200);
should.exist(res.header["content-type"]);
should.exist(res.header["server"]);
res.header["content-type"].should.match(new RegExp("json", "g"));
res.header["server"].should.match(new RegExp("postgrest", "g"));
console.log(res.body);
should.exist(res.body.settings);
should.exist(res.body.settings.preferences.badges);
let badges = res.body.settings.preferences.badges;
//console.log(Object.keys(badges));
Object.keys(badges).length.should.be.aboveOrEqual(3);
badges.should.have.properties(
"Helmsman",
"Wake Maker",
"Stormtrooper"
);
done(err);
});
});
}); // user JWT
describe("Function monitoring endpoint, JWT user_role", function () {
let otp = null;
test.monitoring.forEach(function (subtest) {
it(`${subtest.url}`, function (done) {
try {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.get(subtest.url)
.set("Authorization", `Bearer ${user_jwt}`)
.set("Accept", "application/json")
.end(function (err, res) {
res.status.should.equal(200);
should.exist(res.header["content-type"]);
should.exist(res.header["server"]);
res.header["content-type"].should.match(
new RegExp("json", "g")
);
res.header["server"].should.match(new RegExp("postgrest", "g"));
//console.log(res.body);
should.exist(res.body);
//let monitoring = res.body;
//console.log(monitoring);
// minimum set for static monitoring page
// no value for humidity monitoring
//monitoring.length.should.be.aboveOrEqual(21);
done(err);
});
} catch (error) {
done();
}
});
});
}); // Monitoring endpoint
describe("Event Logs, user jwt", function () {
it("/eventlogs_view endpoint, list process_queue, JWT user_role", function (done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.get("/eventlogs_view")
.set("Authorization", `Bearer ${user_jwt}`)
.set("Accept", "application/json")
.end(function (err, res) {
res.status.should.equal(200);
should.exist(res.header["content-type"]);
should.exist(res.header["server"]);
res.header["content-type"].should.match(new RegExp("json", "g"));
res.header["server"].should.match(new RegExp("postgrest", "g"));
//console.log(res.body);
should.exist(res.body);
let event = res.body;
//console.log(event);
// minimum events log for kapla & aava 13 + 4 email_otp = 17
event.length.should.be.aboveOrEqual(13);
done(err);
});
});
}); // user JWT
describe("Function update preference for public access endpoint, JWT user_role", function () {
test.public.forEach(function (subtest) {
it(`${subtest.url}`, function (done) {
try {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post(subtest.url)
.send(subtest.payload)
.set("Authorization", `Bearer ${user_jwt}`)
.set("Accept", "application/json")
.end(function (err, res) {
res.status.should.equal(200);
should.exist(res.header["content-type"]);
should.exist(res.header["server"]);
res.header["server"].should.match(new RegExp("postgrest", "g"));
//console.log(res.body);
should.exist(res.body);
//let monitoring = res.body;
//console.log(monitoring);
// minimum set for static monitoring page
// no value for humidity monitoring
//monitoring.length.should.be.aboveOrEqual(21);
done(err);
});
} catch (error) {
done();
}
});
});
}); // Monitoring endpoint
}); // OpenAPI description
}); // Users Array

187
tests/index5.js Normal file
View File

@@ -0,0 +1,187 @@
"use strict";
/*
* Unit test #5
* Public/Anonymous access
*
* process.env.PGSAIL_API_URI = from inside the docker
*
* npm install supertest should mocha mochawesome moment
* alias mocha="./node_modules/mocha/bin/_mocha"
* mocha index5.js --reporter mochawesome --reporter-options reportDir=/mnt/postgsail/,reportFilename=report_api.html
*
*/
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const supertest = require("supertest");
// Deprecated
const should = require("should");
//const chai = require("chai");
//const should = chai.should();
let request = null;
var moment = require("moment");
// Users Array
[
{
cname: process.env.PGSAIL_API_URI,
name: "PostgSail unit test kapla",
logs: {
url: "/logs_view",
header: { name: "x-is-public", value: btoa("kapla,public_logs_list,0") },
payload: null,
res: {},
},
log: {
url: "/log_view?id=eq.1",
header: { name: "x-is-public", value: btoa("kapla,public_logs,1") },
payload: null,
res: {},
},
monitoring: {
url: "/monitoring_view",
header: { name: "x-is-public", value: btoa("kapla,public_monitoring,0") },
payload: null,
res: {},
},
timelapse: {
url: "/rpc/timelapse_fn",
header: { name: "x-is-public", value: btoa("kapla,public_timelapse,1") },
payload: null,
res: {},
},
export_gpx: {
url: "/rpc/export_logbook_gpx_fn",
header: { name: "x-is-public", value: btoa("kapla,public_logs,0") },
payload: null,
res: {},
},
},
{
cname: process.env.PGSAIL_API_URI,
name: "PostgSail unit test, aava",
logs: {
url: "/logs_view",
header: { name: "x-is-public", value: btoa("aava,public_logs_list,0") },
payload: null,
res: {},
},
log: {
url: "/log_view?id=eq.3",
header: { name: "x-is-public", value: btoa("aava,public_logs,3") },
payload: null,
res: {},
},
monitoring: {
url: "/monitoring_view",
header: { name: "x-is-public", value: btoa("aava,public_monitoring,0") },
payload: null,
res: {},
},
timelapse: {
url: "/rpc/timelapse_fn",
header: { name: "x-is-public", value: btoa("aava,public_timelapse,0") },
payload: null,
res: {},
},
export_gpx: {
url: "/rpc/export_logbook_gpx_fn",
header: { name: "x-is-public", value: btoa("aava,public_logs,0") },
payload: null,
res: {},
},
},
].forEach(function (test) {
//console.log(`${test.cname}`);
describe(`${test.name}`, function () {
request = supertest.agent(test.cname);
request.set("User-Agent", "PostgSail unit tests");
describe("Get JWT api_anonymous", function () {
it("/logs_view, api_anonymous no jwt token", function (done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.get(test.logs.url)
.set(test.logs.header.name, test.logs.header.value)
.set("Accept", "application/json")
.end(function (err, res) {
res.status.should.equal(404);
should.exist(res.header["content-type"]);
should.exist(res.header["server"]);
res.header["content-type"].should.match(new RegExp("json", "g"));
res.header["server"].should.match(new RegExp("postgrest", "g"));
done(err);
});
});
it("/log_view, api_anonymous no jwt token", function (done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.get(test.log.url)
.set(test.log.header.name, test.log.header.value)
.set("Accept", "application/json")
.end(function (err, res) {
res.status.should.equal(200);
should.exist(res.header["content-type"]);
should.exist(res.header["server"]);
res.header["content-type"].should.match(new RegExp("json", "g"));
res.header["server"].should.match(new RegExp("postgrest", "g"));
done(err);
});
});
it("/monitoring_view, api_anonymous no jwt token", function (done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.get(test.monitoring.url)
.set(test.monitoring.header.name, test.monitoring.header.value)
.set("Accept", "application/json")
.end(function (err, res) {
console.log(res.text);
res.status.should.equal(200);
should.exist(res.header["content-type"]);
should.exist(res.header["server"]);
res.header["content-type"].should.match(new RegExp("json", "g"));
res.header["server"].should.match(new RegExp("postgrest", "g"));
done(err);
});
});
it("/rpc/timelapse_fn, api_anonymous no jwt token", function (done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post(test.timelapse.url)
.set(test.timelapse.header.name, test.timelapse.header.value)
.set("Accept", "application/json")
.end(function (err, res) {
console.log(res.text);
res.status.should.equal(404);
should.exist(res.header["content-type"]);
should.exist(res.header["server"]);
res.header["content-type"].should.match(new RegExp("json", "g"));
res.header["server"].should.match(new RegExp("postgrest", "g"));
done(err);
});
});
it("/rpc/export_logbook_gpx_fn, api_anonymous no jwt token", function (done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post(test.export_gpx.url)
.send({_id: 1})
.set(test.export_gpx.header.name, test.export_gpx.header.value)
.set("Accept", "application/json")
.end(function (err, res) {
console.log(res.text)
res.status.should.equal(401);
should.exist(res.header["content-type"]);
should.exist(res.header["server"]);
res.header["content-type"].should.match(new RegExp("json", "g"));
res.header["server"].should.match(new RegExp("postgrest", "g"));
done(err);
});
});
}); // user JWT
}); // OpenAPI description
}); // Users Array

View File

@@ -0,0 +1,579 @@
{
"metrics": [
{
"time" : "2022-07-31T11:28:13.331Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7231605,
"longitude" : 24.7358807,
"speedoverground" : 7.1,
"courseovergroundtrue" : 188.9,
"windspeedapparent" : 13.9,
"anglespeedapparent" : 56.0,
"status" : "moored",
"metrics" : {"navigation.log": 17441210, "navigation.trip.log": 80099, "navigation.headingTrue": 3.3179, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 36.219, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.48, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 19.6, "electrical.batteries.1.voltage": 13.38, "navigation.gnss.antennaAltitude": 2.21, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 36.22, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 89, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 4, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 9, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:29:13.340Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7213961,
"longitude" : 24.7349507,
"speedoverground" : 6.5,
"courseovergroundtrue" : 197.4,
"windspeedapparent" : 15.4,
"anglespeedapparent" : 43.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17441395, "navigation.trip.log": 80284, "navigation.headingTrue": 3.4924, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 32.289, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.34, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 231, "electrical.batteries.1.voltage": 14.45, "navigation.gnss.antennaAltitude": -0.04, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 32.29, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 57, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 4, "network.n2k.ngt-1.130356.ch1.bandwidth": 3, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 5, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 11, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:29:13.340Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7213961,
"longitude" : 24.7349507,
"speedoverground" : 6.5,
"courseovergroundtrue" : 197.4,
"windspeedapparent" : 15.4,
"anglespeedapparent" : 43.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17441395, "navigation.trip.log": 80284, "navigation.headingTrue": 3.4924, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 32.289, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.34, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 231, "electrical.batteries.1.voltage": 14.45, "navigation.gnss.antennaAltitude": -0.04, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 32.29, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 57, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 4, "network.n2k.ngt-1.130356.ch1.bandwidth": 3, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 5, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 11, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:30:28.338Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7190763,
"longitude" : 24.733775,
"speedoverground" : 7.1,
"courseovergroundtrue" : 194.4,
"windspeedapparent" : 16.3,
"anglespeedapparent" : 41.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17441580, "navigation.trip.log": 80544, "navigation.headingTrue": 3.4226, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 23.808999999999997, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.61, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 231.5, "electrical.batteries.1.voltage": 13.02, "navigation.gnss.antennaAltitude": 2.06, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 23.81, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 73, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 5, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 1, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 1, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:31:28.348Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7173052,
"longitude" : 24.7325741,
"speedoverground" : 6.5,
"courseovergroundtrue" : 198.8,
"windspeedapparent" : 15.8,
"anglespeedapparent" : 41.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17441766, "navigation.trip.log": 80747, "navigation.headingTrue": 3.5972, "navigation.gnss.satellites": 10, "environment.depth.belowKeel": 20.948999999999998, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.47, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 192.4, "electrical.batteries.1.voltage": 14.56, "navigation.gnss.antennaAltitude": 0.21, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 20.95, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 64, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 4, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:32:28.364Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7156422,
"longitude" : 24.731211599999998,
"speedoverground" : 6.4,
"courseovergroundtrue" : 203.3,
"windspeedapparent" : 13.7,
"anglespeedapparent" : 58.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17442136, "navigation.trip.log": 80951, "navigation.headingTrue": 3.5361, "navigation.gnss.satellites": 10, "environment.depth.belowKeel": 17.529, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 2.93, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 105.2, "electrical.batteries.1.voltage": 14.55, "navigation.gnss.antennaAltitude": 2.49, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 17.53, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 2, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 43, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 4, "network.n2k.ngt-1.130356.ch1.bandwidth": 3, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 5, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 11, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:33:28.377Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7139966,
"longitude" : 24.7299656,
"speedoverground" : 6.5,
"courseovergroundtrue" : 200.4,
"windspeedapparent" : 15.6,
"anglespeedapparent" : 44.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17442321, "navigation.trip.log": 81136, "navigation.headingTrue": 3.571, "navigation.gnss.satellites": 10, "environment.depth.belowKeel": 13.999, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.21, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 70.4, "electrical.batteries.1.voltage": 14.56, "navigation.gnss.antennaAltitude": 2.69, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 14, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 34, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 4, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:34:28.396Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7124464,
"longitude" : 24.728924,
"speedoverground" : 5.8,
"courseovergroundtrue" : 197.8,
"windspeedapparent" : 12.2,
"anglespeedapparent" : 31.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17442506, "navigation.trip.log": 81321, "navigation.headingTrue": 3.5535, "navigation.gnss.satellites": 10, "environment.depth.belowKeel": 13.999, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.07, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 43.9, "electrical.batteries.1.voltage": 14.54, "navigation.gnss.antennaAltitude": 2.11, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 14, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 66, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 4, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:35:28.413Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7106379,
"longitude" : 24.728088,
"speedoverground" : 6.7,
"courseovergroundtrue" : 187.8,
"windspeedapparent" : 13.7,
"anglespeedapparent" : 42.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17442692, "navigation.trip.log": 81507, "navigation.headingTrue": 3.4313, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 14.029, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.21, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 36.1, "electrical.batteries.1.voltage": 14.56, "navigation.gnss.antennaAltitude": 2.78, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 14.03, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 69, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 5, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:36:28.424Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7087125,
"longitude" : 24.7276837,
"speedoverground" : 7.1,
"courseovergroundtrue" : 185.4,
"windspeedapparent" : 13.5,
"anglespeedapparent" : 51.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17442877, "navigation.trip.log": 81692, "navigation.headingTrue": 3.3004, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 22.689, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.34, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 25.2, "electrical.batteries.1.voltage": 14.55, "navigation.gnss.antennaAltitude": 3.24, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 22.69, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 2, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 45, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 5, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:37:28.444Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7245499,
"longitude" : 24.736394999999998,
"speedoverground" : 6.7,
"courseovergroundtrue" : 196.5,
"windspeedapparent" : 14.1,
"anglespeedapparent" : 42.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17441025, "navigation.trip.log": 79951, "navigation.headingTrue": 3.475, "navigation.gnss.satellites": 10, "environment.depth.belowKeel": 71.039, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.34, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 16.4, "electrical.batteries.1.voltage": 14.54, "navigation.gnss.antennaAltitude": 2.32, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 71.04, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 64, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 4, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:38:28.483Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7227198,
"longitude" : 24.735699,
"speedoverground" : 6.4,
"courseovergroundtrue" : 186.4,
"windspeedapparent" : 13.9,
"anglespeedapparent" : 44.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17441210, "navigation.trip.log": 80136, "navigation.headingTrue": 3.4401, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 33.169000000000004, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.21, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 172, "electrical.batteries.1.voltage": 13.35, "navigation.gnss.antennaAltitude": 2.11, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 33.17, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 32, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 5, "network.n2k.ngt-1.130356.ch1.bandwidth": 3, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 6, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 13, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 1, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 1, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:39:28.509Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7209298,
"longitude" : 24.73472,
"speedoverground" : 6.8,
"courseovergroundtrue" : 190.7,
"windspeedapparent" : 15.4,
"anglespeedapparent" : 38.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17441395, "navigation.trip.log": 80340, "navigation.headingTrue": 3.4663, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 30.738999999999997, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.47, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 211.6, "electrical.batteries.1.voltage": 14.54, "navigation.gnss.antennaAltitude": 0.76, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 30.74, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 2, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 52, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 4, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 9, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:40:28.539Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7190763,
"longitude" : 24.733774999999998,
"speedoverground" : 7.1,
"courseovergroundtrue" : 194.4,
"windspeedapparent" : 16.3,
"anglespeedapparent" : 41.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17441580, "navigation.trip.log": 80544, "navigation.headingTrue": 3.4226, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 23.808999999999997, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.61, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 231.5, "electrical.batteries.1.voltage": 13.02, "navigation.gnss.antennaAltitude": 2.06, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 23.81, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 73, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 5, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 1, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 1, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:41:28.561Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7173052,
"longitude" : 24.7325741,
"speedoverground" : 6.5,
"courseovergroundtrue" : 198.8,
"windspeedapparent" : 15.8,
"anglespeedapparent" : 41.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17441766, "navigation.trip.log": 80747, "navigation.headingTrue": 3.5972, "navigation.gnss.satellites": 10, "environment.depth.belowKeel": 20.948999999999998, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.47, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 192.4, "electrical.batteries.1.voltage": 14.56, "navigation.gnss.antennaAltitude": 0.39, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 20.95, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 64, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 4, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:42:28.569Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7156422,
"longitude" : 24.7312116,
"speedoverground" : 6.4,
"courseovergroundtrue" : 203.3,
"windspeedapparent" : 13.7,
"anglespeedapparent" : 58.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17442136, "navigation.trip.log": 80951, "navigation.headingTrue": 3.5361, "navigation.gnss.satellites": 10, "environment.depth.belowKeel": 17.529, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 2.93, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 107.6, "electrical.batteries.1.voltage": 14.55, "navigation.gnss.antennaAltitude": 2.49, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 17.53, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 2, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 43, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 4, "network.n2k.ngt-1.130356.ch1.bandwidth": 3, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 5, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 11, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:43:28.603Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7139966,
"longitude" : 24.7299656,
"speedoverground" : 6.4,
"courseovergroundtrue" : 200.4,
"windspeedapparent" : 15.6,
"anglespeedapparent" : 44.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17442321, "navigation.trip.log": 81136, "navigation.headingTrue": 3.571, "navigation.gnss.satellites": 10, "environment.depth.belowKeel": 13.999, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.21, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 70.4, "electrical.batteries.1.voltage": 14.56, "navigation.gnss.antennaAltitude": 2.69, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 14, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 34, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 4, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:44:28.629Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7124464,
"longitude" : 24.728924,
"speedoverground" : 5.7,
"courseovergroundtrue" : 197.6,
"windspeedapparent" : 12.2,
"anglespeedapparent" : 31.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17442506, "navigation.trip.log": 81321, "navigation.headingTrue": 3.571, "navigation.gnss.satellites": 10, "environment.depth.belowKeel": 13.999, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.07, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 43.9, "electrical.batteries.1.voltage": 14.54, "navigation.gnss.antennaAltitude": 2.05, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 14, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 66, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 4, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:45:28.645Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7106379,
"longitude" : 24.728088,
"speedoverground" : 6.7,
"courseovergroundtrue" : 187.8,
"windspeedapparent" : 13.7,
"anglespeedapparent" : 42.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17442692, "navigation.trip.log": 81507, "navigation.headingTrue": 3.4313, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 14.029, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.21, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 36.1, "electrical.batteries.1.voltage": 14.56, "navigation.gnss.antennaAltitude": 2.78, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 14.03, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 69, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 5, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:46:28.664Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7087125,
"longitude" : 24.7276837,
"speedoverground" : 7.0,
"courseovergroundtrue" : 185.4,
"windspeedapparent" : 13.5,
"anglespeedapparent" : 51.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17442877, "navigation.trip.log": 81692, "navigation.headingTrue": 3.3004, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 22.689, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.34, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 25.2, "electrical.batteries.1.voltage": 14.55, "navigation.gnss.antennaAltitude": 3.24, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 22.69, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 2, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 45, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 5, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:47:28.696Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7245499,
"longitude" : 24.736394999999998,
"speedoverground" : 6.7,
"courseovergroundtrue" : 196.5,
"windspeedapparent" : 14.1,
"anglespeedapparent" : 42.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17441025, "navigation.trip.log": 79951, "navigation.headingTrue": 3.475, "navigation.gnss.satellites": 10, "environment.depth.belowKeel": 71.039, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.34, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 16.4, "electrical.batteries.1.voltage": 14.54, "navigation.gnss.antennaAltitude": 2.32, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 71.04, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 64, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 4, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:48:28.712Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7226901,
"longitude" : 24.735683899999998,
"speedoverground" : 6.4,
"courseovergroundtrue" : 187.7,
"windspeedapparent" : 14.9,
"anglespeedapparent" : 44.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17441210, "navigation.trip.log": 80136, "navigation.headingTrue": 3.4313, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 33.169000000000004, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.21, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 172, "electrical.batteries.1.voltage": 13.35, "navigation.gnss.antennaAltitude": 2.13, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 33.17, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 32, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 5, "network.n2k.ngt-1.130356.ch1.bandwidth": 3, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 6, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 13, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 1, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 1, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:49:28.726Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7209298,
"longitude" : 24.73472,
"speedoverground" : 6.8,
"courseovergroundtrue" : 190.7,
"windspeedapparent" : 15.4,
"anglespeedapparent" : 38.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17441395, "navigation.trip.log": 80340, "navigation.headingTrue": 3.4663, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 30.738999999999997, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.47, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 211.6, "electrical.batteries.1.voltage": 14.54, "navigation.gnss.antennaAltitude": 0.76, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 30.74, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 2, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 52, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 4, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 9, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:50:28.743Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7190454,
"longitude" : 24.7337548,
"speedoverground" : 7.0,
"courseovergroundtrue" : 194.4,
"windspeedapparent" : 16.3,
"anglespeedapparent" : 41.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17441580, "navigation.trip.log": 80544, "navigation.headingTrue": 3.4226, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 23.808999999999997, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.61, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 231.5, "electrical.batteries.1.voltage": 13.02, "navigation.gnss.antennaAltitude": 2.06, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 23.81, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 73, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 5, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 1, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 1, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:51:28.762Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7172775,
"longitude" : 24.7325491,
"speedoverground" : 6.5,
"courseovergroundtrue" : 198.3,
"windspeedapparent" : 13.9,
"anglespeedapparent" : 41.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17441766, "navigation.trip.log": 80747, "navigation.headingTrue": 3.5797, "navigation.gnss.satellites": 10, "environment.depth.belowKeel": 20.948999999999998, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.47, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 192.4, "electrical.batteries.1.voltage": 14.56, "navigation.gnss.antennaAltitude": 0.39, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 20.95, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 64, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 4, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:52:28.783Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7156422,
"longitude" : 24.7312116,
"speedoverground" : 6.4,
"courseovergroundtrue" : 203.3,
"windspeedapparent" : 15.6,
"anglespeedapparent" : 58.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17442136, "navigation.trip.log": 80951, "navigation.headingTrue": 3.5361, "navigation.gnss.satellites": 10, "environment.depth.belowKeel": 17.529, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 2.93, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 107.6, "electrical.batteries.1.voltage": 14.55, "navigation.gnss.antennaAltitude": 2.49, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 17.53, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 2, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 43, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 4, "network.n2k.ngt-1.130356.ch1.bandwidth": 3, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 5, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 11, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:53:28.800Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7139716,
"longitude" : 24.7299463,
"speedoverground" : 6.4,
"courseovergroundtrue" : 200.4,
"windspeedapparent" : 14.1,
"anglespeedapparent" : 44.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17442321, "navigation.trip.log": 81136, "navigation.headingTrue": 3.571, "navigation.gnss.satellites": 10, "environment.depth.belowKeel": 13.999, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.21, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 70.4, "electrical.batteries.1.voltage": 14.56, "navigation.gnss.antennaAltitude": 2.69, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 14, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 34, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 4, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:54:28.819Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7124464,
"longitude" : 24.728924,
"speedoverground" : 5.7,
"courseovergroundtrue" : 197.6,
"windspeedapparent" : 15.9,
"anglespeedapparent" : 31.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17442506, "navigation.trip.log": 81321, "navigation.headingTrue": 3.571, "navigation.gnss.satellites": 10, "environment.depth.belowKeel": 13.999, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.07, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 43.9, "electrical.batteries.1.voltage": 14.54, "navigation.gnss.antennaAltitude": 2.05, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 14, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 66, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 4, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:55:28.837Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7106379,
"longitude" : 24.728088,
"speedoverground" : 6.7,
"courseovergroundtrue" : 187.8,
"windspeedapparent" : 16.9,
"anglespeedapparent" : 42.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17442692, "navigation.trip.log": 81507, "navigation.headingTrue": 3.4313, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 14.029, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.21, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 36.1, "electrical.batteries.1.voltage": 14.56, "navigation.gnss.antennaAltitude": 2.74, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 14.03, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 69, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 5, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:56:28.855Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7086808,
"longitude" : 24.7276789,
"speedoverground" : 7.0,
"courseovergroundtrue" : 185.4,
"windspeedapparent" : 13.5,
"anglespeedapparent" : 51.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17442877, "navigation.trip.log": 81692, "navigation.headingTrue": 3.3004, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 22.689, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.34, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 25.2, "electrical.batteries.1.voltage": 14.55, "navigation.gnss.antennaAltitude": 3.24, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 22.69, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 2, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 45, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 5, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:57:28.876Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7245198,
"longitude" : 24.7363702,
"speedoverground" : 6.7,
"courseovergroundtrue" : 196.5,
"windspeedapparent" : 14.1,
"anglespeedapparent" : 42.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17441025, "navigation.trip.log": 79951, "navigation.headingTrue": 3.475, "navigation.gnss.satellites": 10, "environment.depth.belowKeel": 71.039, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.34, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 16.4, "electrical.batteries.1.voltage": 14.54, "navigation.gnss.antennaAltitude": 2.32, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 71.04, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 64, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 4, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:58:28.899Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7226901,
"longitude" : 24.735683899999998,
"speedoverground" : 6.4,
"courseovergroundtrue" : 187.7,
"windspeedapparent" : 14.9,
"anglespeedapparent" : 44.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17441210, "navigation.trip.log": 80136, "navigation.headingTrue": 3.4313, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 33.169000000000004, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.21, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 172, "electrical.batteries.1.voltage": 13.35, "navigation.gnss.antennaAltitude": 2.13, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 33.17, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 32, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 5, "network.n2k.ngt-1.130356.ch1.bandwidth": 3, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 6, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 13, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 1, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 1, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:59:28.928Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7208986,
"longitude" : 24.734705599999998,
"speedoverground" : 6.8,
"courseovergroundtrue" : 190.7,
"windspeedapparent" : 15.4,
"anglespeedapparent" : 38.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17441395, "navigation.trip.log": 80358, "navigation.headingTrue": 3.5099, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 30.738999999999997, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.47, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 211.6, "electrical.batteries.1.voltage": 14.54, "navigation.gnss.antennaAltitude": 0.78, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 30.74, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 2, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 52, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 4, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 9, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T12:00:28.941Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7190454,
"longitude" : 24.7337548,
"speedoverground" : 7.0,
"courseovergroundtrue" : 194.4,
"windspeedapparent" : 17.0,
"anglespeedapparent" : 41.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17441580, "navigation.trip.log": 80544, "navigation.headingTrue": 3.4226, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 23.808999999999997, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.61, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 231.5, "electrical.batteries.1.voltage": 13.02, "navigation.gnss.antennaAltitude": 2.06, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 23.81, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 73, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 5, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 1, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 1, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T12:01:28.966Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7172775,
"longitude" : 24.7325491,
"speedoverground" : 6.5,
"courseovergroundtrue" : 198.3,
"windspeedapparent" : 15.1,
"anglespeedapparent" : 41.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17441766, "navigation.trip.log": 80747, "navigation.headingTrue": 3.5797, "navigation.gnss.satellites": 10, "environment.depth.belowKeel": 20.948999999999998, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.47, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 192.4, "electrical.batteries.1.voltage": 14.56, "navigation.gnss.antennaAltitude": 0.39, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 20.95, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 64, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 4, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T12:02:28.982Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7156162,
"longitude" : 24.731190599999998,
"speedoverground" : 6.4,
"courseovergroundtrue" : 203.1,
"windspeedapparent" : 15.6,
"anglespeedapparent" : 58.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17442136, "navigation.trip.log": 80951, "navigation.headingTrue": 3.5273, "navigation.gnss.satellites": 10, "environment.depth.belowKeel": 17.529, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 2.93, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 107.6, "electrical.batteries.1.voltage": 14.55, "navigation.gnss.antennaAltitude": 2.6, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 17.53, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 2, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 43, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 4, "network.n2k.ngt-1.130356.ch1.bandwidth": 3, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 5, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 11, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T12:03:28.988Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7139716,
"longitude" : 24.7299463,
"speedoverground" : 6.4,
"courseovergroundtrue" : 200.4,
"windspeedapparent" : 14.1,
"anglespeedapparent" : 44.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17442321, "navigation.trip.log": 81136, "navigation.headingTrue": 3.571, "navigation.gnss.satellites": 10, "environment.depth.belowKeel": 13.999, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.07, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 70.4, "electrical.batteries.1.voltage": 14.56, "navigation.gnss.antennaAltitude": 2.69, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 14, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 34, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 4, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T12:04:29.008Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7124174,
"longitude" : 24.7289112,
"speedoverground" : 5.7,
"courseovergroundtrue" : 197.6,
"windspeedapparent" : 15.9,
"anglespeedapparent" : 31.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17442506, "navigation.trip.log": 81321, "navigation.headingTrue": 3.571, "navigation.gnss.satellites": 10, "environment.depth.belowKeel": 13.749, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.07, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 43.9, "electrical.batteries.1.voltage": 14.54, "navigation.gnss.antennaAltitude": 2.05, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 13.75, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 66, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 4, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T12:05:29.025Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7106066,
"longitude" : 24.7280837,
"speedoverground" : 6.7,
"courseovergroundtrue" : 191.2,
"windspeedapparent" : 16.9,
"anglespeedapparent" : 42.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17442692, "navigation.trip.log": 81507, "navigation.headingTrue": 3.4313, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 13.539, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.34, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 36.1, "electrical.batteries.1.voltage": 14.56, "navigation.gnss.antennaAltitude": 2.74, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 13.54, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 69, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 5, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T12:06:29.043Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7086808,
"longitude" : 24.7276789,
"speedoverground" : 7.0,
"courseovergroundtrue" : 185.4,
"windspeedapparent" : 14.4,
"anglespeedapparent" : 51.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17442877, "navigation.trip.log": 81710, "navigation.headingTrue": 3.3004, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 22.689, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.34, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 25.2, "electrical.batteries.1.voltage": 14.55, "navigation.gnss.antennaAltitude": 3.24, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 22.69, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 2, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 45, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 5, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T12:07:29.071Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7245198,
"longitude" : 24.7363702,
"speedoverground" : 6.7,
"courseovergroundtrue" : 196.5,
"windspeedapparent" : 14.1,
"anglespeedapparent" : 42.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17441025, "navigation.trip.log": 79951, "navigation.headingTrue": 3.475, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 71.039, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.19, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 16.4, "electrical.batteries.1.voltage": 14.54, "navigation.gnss.antennaAltitude": 2.51, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 71.04, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 64, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 4, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T12:08:29.081Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7226901,
"longitude" : 24.7356839,
"speedoverground" : 6.4,
"courseovergroundtrue" : 187.7,
"windspeedapparent" : 14.9,
"anglespeedapparent" : 44.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17441210, "navigation.trip.log": 80155, "navigation.headingTrue": 3.4313, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 33.719, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.09, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 172, "electrical.batteries.1.voltage": 13.35, "navigation.gnss.antennaAltitude": 2.13, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 33.72, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 72, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 3, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 6, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 13, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 1, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 1, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T12:09:29.094Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7208986,
"longitude" : 24.734705599999998,
"speedoverground" : 6.8,
"courseovergroundtrue" : 191.3,
"windspeedapparent" : 15.4,
"anglespeedapparent" : 38.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17441395, "navigation.trip.log": 80358, "navigation.headingTrue": 3.5099, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 31.529, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.47, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 211.6, "electrical.batteries.1.voltage": 14.54, "navigation.gnss.antennaAltitude": 0.78, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 31.53, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 2, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 49, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 4, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 9, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T12:10:29.108Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7190454,
"longitude" : 24.7337548,
"speedoverground" : 7.0,
"courseovergroundtrue" : 194.4,
"windspeedapparent" : 17.0,
"anglespeedapparent" : 41.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17441580, "navigation.trip.log": 80544, "navigation.headingTrue": 3.4226, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 25.398999999999997, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.47, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 231.5, "electrical.batteries.1.voltage": 13.02, "navigation.gnss.antennaAltitude": 1.8, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 25.4, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 57, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 5, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 1, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 1, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T12:11:29.120Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7172775,
"longitude" : 24.7325491,
"speedoverground" : 6.5,
"courseovergroundtrue" : 198.3,
"windspeedapparent" : 15.1,
"anglespeedapparent" : 41.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17441766, "navigation.trip.log": 80747, "navigation.headingTrue": 3.5797, "navigation.gnss.satellites": 10, "environment.depth.belowKeel": 20.948999999999998, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.34, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 192.4, "electrical.batteries.1.voltage": 14.56, "navigation.gnss.antennaAltitude": 0.39, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 20.95, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 81, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 4, "network.n2k.ngt-1.130356.ch1.bandwidth": 3, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 4, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T12:12:29.137Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7156162,
"longitude" : 24.731190599999998,
"speedoverground" : 6.4,
"courseovergroundtrue" : 203.1,
"windspeedapparent" : 15.6,
"anglespeedapparent" : 58.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17442136, "navigation.trip.log": 80951, "navigation.headingTrue": 3.5273, "navigation.gnss.satellites": 10, "environment.depth.belowKeel": 17.529, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.21, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 107.6, "electrical.batteries.1.voltage": 14.55, "navigation.gnss.antennaAltitude": 2.6, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 17.53, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 2, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 55, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 5, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 11, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T12:13:29.150Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7139716,
"longitude" : 24.729946299999998,
"speedoverground" : 6.4,
"courseovergroundtrue" : 200.7,
"windspeedapparent" : 14.1,
"anglespeedapparent" : 44.0,
"status" : "anchored",
"metrics" : {"navigation.log": 17442321, "navigation.trip.log": 81136, "navigation.headingTrue": 3.571, "navigation.gnss.satellites": 10, "environment.depth.belowKeel": 14.209000000000001, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.07, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 70.4, "electrical.batteries.1.voltage": 14.56, "navigation.gnss.antennaAltitude": 2.38, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 14.21, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 70, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 4, "network.n2k.ngt-1.130356.ch1.bandwidth": 3, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 4, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T12:14:29.168Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7124174,
"longitude" : 24.7289112,
"speedoverground" : 5.7,
"courseovergroundtrue" : 197.6,
"windspeedapparent" : 15.9,
"anglespeedapparent" : 31.0,
"status" : "anchored",
"metrics" : {"navigation.log": 17442506, "navigation.trip.log": 81321, "navigation.headingTrue": 3.571, "navigation.gnss.satellites": 10, "environment.depth.belowKeel": 13.749, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.07, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 43.9, "electrical.batteries.1.voltage": 14.54, "navigation.gnss.antennaAltitude": 2.05, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 13.75, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 40, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 4, "network.n2k.ngt-1.130356.ch1.bandwidth": 3, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 5, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
}
]}

View File

@@ -0,0 +1,639 @@
{
"metrics": [
{
"time" : "2022-07-30T14:52:28.000Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 60.077666666666666,
"longitude" : 23.530866666666668,
"speedoverground" : 0.0,
"courseovergroundtrue" : 207.5,
"windspeedapparent" : 14.8,
"anglespeedapparent" : -12.0,
"status" : "moored",
"metrics" : {"environment.wind.speedTrue": 4.44, "navigation.speedThroughWater": 3.0918118943701245, "performance.velocityMadeGood": 2.9323340761912995, "environment.wind.angleTrueWater": -0.3665191430024964, "environment.depth.belowTransducer": 13.1, "navigation.courseOverGroundMagnetic": 3.620685534088946, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T14:52:28.000Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 60.077666666666666,
"longitude" : 23.530866666666668,
"speedoverground" : 0.0,
"courseovergroundtrue" : 207.5,
"windspeedapparent" : 14.8,
"anglespeedapparent" : -12.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 4.44, "navigation.speedThroughWater": 3.0918118943701245, "performance.velocityMadeGood": 2.9323340761912995, "environment.wind.angleTrueWater": -0.3665191430024964, "environment.depth.belowTransducer": 13.1, "navigation.courseOverGroundMagnetic": 3.620685534088946, "navigation.courseRhumbline.crossTrackError": 0, "propulsion.main.runTime":1776241 }
},
{
"time" : "2022-07-30T14:53:28.000Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 60.077666666666666,
"longitude" : 23.530866666666668,
"speedoverground" : 5.7,
"courseovergroundtrue" : 207.5,
"windspeedapparent" : 14.8,
"anglespeedapparent" : -12.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 4.44, "navigation.speedThroughWater": 3.0918118943701245, "performance.velocityMadeGood": 2.9323340761912995, "environment.wind.angleTrueWater": -0.3665191430024964, "environment.depth.belowTransducer": 13.1, "navigation.courseOverGroundMagnetic": 3.620685534088946, "navigation.courseRhumbline.crossTrackError": 0 }
},
{
"time" : "2022-07-30T14:54:28.016Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 60.07065,
"longitude" : 23.52355,
"speedoverground" : 5.6,
"courseovergroundtrue" : 211.5,
"windspeedapparent" : 14.8,
"anglespeedapparent" : -7.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 4.52, "navigation.speedThroughWater": 3.0815230028747167, "performance.velocityMadeGood": 2.9683451964252274, "environment.wind.angleTrueWater": -0.20943951028714078, "environment.depth.belowTransducer": 13.58, "navigation.courseOverGroundMagnetic": 3.6910223029603775, "navigation.courseRhumbline.crossTrackError": 0, "propulsion.main.runTime":1776241 }
},
{
"time" : "2022-07-30T14:55:28.021Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 60.0637,
"longitude" : 23.515866666666668,
"speedoverground" : 5.6,
"courseovergroundtrue" : 211.3,
"windspeedapparent" : 15.7,
"anglespeedapparent" : 0.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 4.99, "navigation.speedThroughWater": 3.0558007741361966, "performance.velocityMadeGood": 3.00950076240686, "environment.wind.angleTrueWater": 0, "environment.depth.belowTransducer": 16.75, "navigation.courseOverGroundMagnetic": 3.6878807103060707, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T14:56:28.033Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 60.05671666666667,
"longitude" : 23.507866666666665,
"speedoverground" : 5.9,
"courseovergroundtrue" : 211.1,
"windspeedapparent" : 19.8,
"anglespeedapparent" : -2.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 4.22, "navigation.speedThroughWater": 3.1895563635765014, "performance.velocityMadeGood": 3.1586896890902767, "environment.wind.angleTrueWater": -0.0174532925239284, "environment.depth.belowTransducer": 18.96, "navigation.courseOverGroundMagnetic": 3.683866453025567, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T14:57:28.049Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 60.04915,
"longitude" : 23.500533333333333,
"speedoverground" : 6.1,
"courseovergroundtrue" : 212.0,
"windspeedapparent" : 14.2,
"anglespeedapparent" : -5.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 2.9, "navigation.speedThroughWater": 3.2204230380627252, "performance.velocityMadeGood": 3.168978580585685, "environment.wind.angleTrueWater": -0.19198621776321237, "environment.depth.belowTransducer": 21.41, "navigation.courseOverGroundMagnetic": 3.6997489492223417, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T14:58:28.064Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 60.04163333333333,
"longitude" : 23.493,
"speedoverground" : 6.0,
"courseovergroundtrue" : 204.9,
"windspeedapparent" : 12.0,
"anglespeedapparent" : -26.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 1.93, "navigation.speedThroughWater": 3.2615786040443577, "performance.velocityMadeGood": 0.8025335366418294, "environment.wind.angleTrueWater": -1.30899693929463, "environment.depth.belowTransducer": 21.01, "navigation.courseOverGroundMagnetic": 3.5753069735267324, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T14:59:28.095Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 60.03398333333333,
"longitude" : 23.485466666666667,
"speedoverground" : 6.1,
"courseovergroundtrue" : 206.0,
"windspeedapparent" : 9.5,
"anglespeedapparent" : -23.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 2.29, "navigation.speedThroughWater": 3.2461452668012454, "performance.velocityMadeGood": 1.6668004222561073, "environment.wind.angleTrueWater": -0.9948376738639186, "environment.depth.belowTransducer": 27.74, "navigation.courseOverGroundMagnetic": 3.595203727004011, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:00:28.106Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 60.02621666666667,
"longitude" : 23.479033333333334,
"speedoverground" : 6.0,
"courseovergroundtrue" : 201.6,
"windspeedapparent" : 10.5,
"anglespeedapparent" : -8.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 2.21, "navigation.speedThroughWater": 3.2410008210535413, "performance.velocityMadeGood": 3.0660896656316043, "environment.wind.angleTrueWater": -0.38397243552642474, "environment.depth.belowTransducer": 31.18, "navigation.courseOverGroundMagnetic": 3.518409239898726, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:01:28.107Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 60.01835,
"longitude" : 23.47295,
"speedoverground" : 6.0,
"courseovergroundtrue" : 197.4,
"windspeedapparent" : 10.9,
"anglespeedapparent" : -13.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 2.07, "navigation.speedThroughWater": 3.2461452668012454, "performance.velocityMadeGood": 2.896322955957371, "environment.wind.angleTrueWater": -0.40142572805035315, "environment.depth.belowTransducer": 34.89, "navigation.courseOverGroundMagnetic": 3.445454477148705, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:02:28.118Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 60.01045,
"longitude" : 23.46745,
"speedoverground" : 6.0,
"courseovergroundtrue" : 201.6,
"windspeedapparent" : 12.8,
"anglespeedapparent" : -26.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 3.98, "navigation.speedThroughWater": 3.2101341465673174, "performance.velocityMadeGood": 2.263556128989775, "environment.wind.angleTrueWater": -0.8203047486246348, "environment.depth.belowTransducer": 23.33, "navigation.courseOverGroundMagnetic": 3.518060174048247, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:03:28.140Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 60.00351666666667,
"longitude" : 23.461033333333333,
"speedoverground" : 5.1,
"courseovergroundtrue" : 210.5,
"windspeedapparent" : 12.0,
"anglespeedapparent" : -8.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 2.34, "navigation.speedThroughWater": 2.69054512604921, "performance.velocityMadeGood": 2.5207784163749767, "environment.wind.angleTrueWater": -0.3665191430024964, "environment.depth.belowTransducer": 18.21, "navigation.courseOverGroundMagnetic": 3.673219944585971, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:04:28.159Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.99755,
"longitude" : 23.45415,
"speedoverground" : 4.8,
"courseovergroundtrue" : 215.7,
"windspeedapparent" : 14.4,
"anglespeedapparent" : 6.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 4.86, "navigation.speedThroughWater": 2.5670784281043133, "performance.velocityMadeGood": 2.546500645113497, "environment.wind.angleTrueWater": 0.20943951028714078, "environment.depth.belowTransducer": 8.97, "navigation.courseOverGroundMagnetic": 3.765198796187073, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:05:28.169Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.99235,
"longitude" : 23.445683333333335,
"speedoverground" : 4.7,
"courseovergroundtrue" : 225.9,
"windspeedapparent" : 16.1,
"anglespeedapparent" : -5.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 6.075590428038464, "navigation.speedThroughWater": 2.5053450791318648, "performance.velocityMadeGood": 2.4641895131502323, "environment.wind.angleTrueWater": -0.12217304766749879, "environment.depth.belowTransducer": 7.03, "navigation.courseOverGroundMagnetic": 3.9426987811554253, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:06:28.196Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.989266666666666,
"longitude" : 23.438766666666666,
"speedoverground" : 1.9,
"courseovergroundtrue" : 223.2,
"windspeedapparent" : 16.7,
"anglespeedapparent" : 3.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 7.61, "navigation.speedThroughWater": 1.0134558122976947, "performance.velocityMadeGood": 0.9825891378114705, "environment.wind.angleTrueWater": 0.052359877571785195, "environment.depth.belowTransducer": 5.78, "navigation.courseOverGroundMagnetic": 3.89522582549034, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:07:28.205Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.98786666666667,
"longitude" : 23.435116666666666,
"speedoverground" : 1.9,
"courseovergroundtrue" : 229.6,
"windspeedapparent" : 18.1,
"anglespeedapparent" : -5.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 8.02, "navigation.speedThroughWater": 1.0186002580453988, "performance.velocityMadeGood": 0.9620113548206545, "environment.wind.angleTrueWater": -0.19198621776321237, "environment.depth.belowTransducer": 5.33, "navigation.courseOverGroundMagnetic": 4.007799562269678, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:08:28.218Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.986333333333334,
"longitude" : 23.43165,
"speedoverground" : 1.4,
"courseovergroundtrue" : 204.7,
"windspeedapparent" : 14.8,
"anglespeedapparent" : 4.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 6.3, "navigation.speedThroughWater": 0.745944633417085, "performance.velocityMadeGood": 0.7408001876693809, "environment.wind.angleTrueWater": 0, "environment.depth.belowTransducer": 5.49, "navigation.courseOverGroundMagnetic": 3.5732125784238606, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:09:28.241Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.984833333333334,
"longitude" : 23.4292,
"speedoverground" : 1.3,
"courseovergroundtrue" : 225.8,
"windspeedapparent" : 13.4,
"anglespeedapparent" : -20.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 4.48, "navigation.speedThroughWater": 0.745944633417085, "performance.velocityMadeGood": 0.7613779706601971, "environment.wind.angleTrueWater": -0.40142572805035315, "environment.depth.belowTransducer": 7.8, "navigation.courseOverGroundMagnetic": 3.9414770506787504, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:10:28.257Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.9862,
"longitude" : 23.432566666666666,
"speedoverground" : 3.3,
"courseovergroundtrue" : 41.1,
"windspeedapparent" : 16.1,
"anglespeedapparent" : 175.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 9.93, "navigation.speedThroughWater": 1.6205004105267706, "performance.velocityMadeGood": -1.6153559647790667, "environment.wind.angleTrueWater": 3.071779484211398, "environment.depth.belowTransducer": 5.41, "navigation.courseOverGroundMagnetic": 0.7171557898082179, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:11:43.288Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.98726666666666,
"longitude" : 23.43375,
"speedoverground" : 1.7,
"courseovergroundtrue" : 228.8,
"windspeedapparent" : 15.7,
"anglespeedapparent" : 8.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 6.37, "navigation.speedThroughWater": 0.9825891378114705, "performance.velocityMadeGood": 0.8694113313619818, "environment.wind.angleTrueWater": 0.15707963271535558, "environment.depth.belowTransducer": 5.31, "navigation.courseOverGroundMagnetic": 3.9934878624000567, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:12:58.309Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.98615,
"longitude" : 23.431566666666665,
"speedoverground" : 1.0,
"courseovergroundtrue" : 223.5,
"windspeedapparent" : 22.1,
"anglespeedapparent" : -6.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 7.63, "navigation.speedThroughWater": 0.6687779472015245, "performance.velocityMadeGood": 0.61218904397678, "environment.wind.angleTrueWater": -0.12217304766749879, "environment.depth.belowTransducer": 5.71, "navigation.courseOverGroundMagnetic": 3.9011599449484757, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:13:58.309Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.98615,
"longitude" : 23.431566666666665,
"speedoverground" : 1.0,
"courseovergroundtrue" : 223.5,
"windspeedapparent" : 22.1,
"anglespeedapparent" : -6.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 7.63, "navigation.speedThroughWater": 0.6687779472015245, "performance.velocityMadeGood": 0.61218904397678, "environment.wind.angleTrueWater": -0.12217304766749879, "environment.depth.belowTransducer": 5.71, "navigation.courseOverGroundMagnetic": 3.9011599449484757, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:14:28.343Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.98565,
"longitude" : 23.4307,
"speedoverground" : 1.2,
"courseovergroundtrue" : 224.5,
"windspeedapparent" : 19.4,
"anglespeedapparent" : 2.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 5.83, "navigation.speedThroughWater": 0.7613779706601971, "performance.velocityMadeGood": 0.7150779589308607, "environment.wind.angleTrueWater": -0.29670597290678274, "environment.depth.belowTransducer": 6.44, "navigation.courseOverGroundMagnetic": 3.9177405728462076, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:15:28.366Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.98468333333334,
"longitude" : 23.429383333333334,
"speedoverground" : 6.1,
"courseovergroundtrue" : 219.9,
"windspeedapparent" : 13.2,
"anglespeedapparent" : 3.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 3.35, "navigation.speedThroughWater": 3.4622119882048152, "performance.velocityMadeGood": 3.32845639876451, "environment.wind.angleTrueWater": 0, "environment.depth.belowTransducer": 8.07, "navigation.courseOverGroundMagnetic": 3.837455427236137, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:16:28.373Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.978233333333336,
"longitude" : 23.42106666666667,
"speedoverground" : 5.3,
"courseovergroundtrue" : 216.7,
"windspeedapparent" : 15.3,
"anglespeedapparent" : -8.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 4.93, "navigation.speedThroughWater": 2.9632007506775238, "performance.velocityMadeGood": 2.772856258012474, "environment.wind.angleTrueWater": -0.20943951028714078, "environment.depth.belowTransducer": 5.16, "navigation.courseOverGroundMagnetic": 3.7824775557857624, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:17:28.386Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.977716666666666,
"longitude" : 23.431,
"speedoverground" : 2.1,
"courseovergroundtrue" : 151.9,
"windspeedapparent" : 12.8,
"anglespeedapparent" : 32.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 2.74, "navigation.speedThroughWater": 1.1214891729994796, "performance.velocityMadeGood": 0.2520778416374977, "environment.wind.angleTrueWater": 1.5533430346296275, "environment.depth.belowTransducer": 3.49, "navigation.courseOverGroundMagnetic": 2.6518532660856806, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:18:28.434Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.97688333333333,
"longitude" : 23.432133333333333,
"speedoverground" : 0.0,
"courseovergroundtrue" : 179.3,
"windspeedapparent" : 11.1,
"anglespeedapparent" : 88.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 3.1895563635765014, "navigation.speedThroughWater": 0, "performance.velocityMadeGood": 0, "environment.wind.angleTrueWater": 1.8151424224885533, "environment.depth.belowTransducer": 1.67, "navigation.courseOverGroundMagnetic": 3.1290262836898832, "navigation.courseRhumbline.crossTrackError": 0 }
},
{
"time" : "2022-07-30T15:19:28.467Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.97688333333333,
"longitude" : 23.4321,
"speedoverground" : 0.0,
"courseovergroundtrue" : 241.0,
"windspeedapparent" : 4.3,
"anglespeedapparent" : 74.0,
"status" : "moored",
"metrics" : {"environment.wind.speedTrue": 0, "navigation.speedThroughWater": 0, "performance.velocityMadeGood": 0, "environment.wind.angleTrueWater": 0.7853981635767779, "environment.depth.belowTransducer": 1.65, "navigation.courseOverGroundMagnetic": 4.206068965341505, "navigation.courseRhumbline.crossTrackError": 0, "propulsion.main.runTime":1776251}
},
{
"time" : "2022-07-30T15:20:28.467Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.97688333333333,
"longitude" : 23.4321,
"speedoverground" : 0.0,
"courseovergroundtrue" : 241.0,
"windspeedapparent" : 4.3,
"anglespeedapparent" : 74.0,
"status" : "moored",
"metrics" : {"environment.wind.speedTrue": 0, "navigation.speedThroughWater": 0, "performance.velocityMadeGood": 0, "environment.wind.angleTrueWater": 0.7853981635767779, "environment.depth.belowTransducer": 1.65, "navigation.courseOverGroundMagnetic": 4.206068965341505, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:20:28.467Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.97688333333333,
"longitude" : 23.4321,
"speedoverground" : 0.0,
"courseovergroundtrue" : 241.0,
"windspeedapparent" : 4.3,
"anglespeedapparent" : 74.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 0, "navigation.speedThroughWater": 0, "performance.velocityMadeGood": 0, "environment.wind.angleTrueWater": 0.7853981635767779, "environment.depth.belowTransducer": 1.65, "navigation.courseOverGroundMagnetic": 4.206068965341505, "navigation.courseRhumbline.crossTrackError": 0, "propulsion.main.runTime":1776251}
},
{
"time" : "2022-07-30T15:21:28.467Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.97688333333333,
"longitude" : 23.4321,
"speedoverground" : 0.0,
"courseovergroundtrue" : 241.0,
"windspeedapparent" : 4.3,
"anglespeedapparent" : 74.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 0, "navigation.speedThroughWater": 0, "performance.velocityMadeGood": 0, "environment.wind.angleTrueWater": 0.7853981635767779, "environment.depth.belowTransducer": 1.65, "navigation.courseOverGroundMagnetic": 4.206068965341505, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:22:28.479Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.9781,
"longitude" : 23.425533333333334,
"speedoverground" : 5.0,
"courseovergroundtrue" : 258.3,
"windspeedapparent" : 9.1,
"anglespeedapparent" : 11.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 2.24, "navigation.speedThroughWater": 2.577367319599721, "performance.velocityMadeGood": 2.3561561524484476, "environment.wind.angleTrueWater": 0.41887902057428156, "environment.depth.belowTransducer": 5.05, "navigation.courseOverGroundMagnetic": 4.507661860154987, "navigation.courseRhumbline.crossTrackError": 0, "propulsion.main.runTime": 1776251}
},
{
"time" : "2022-07-30T15:23:28.492Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.9738,
"longitude" : 23.41165,
"speedoverground" : 6.2,
"courseovergroundtrue" : 228.0,
"windspeedapparent" : 11.8,
"anglespeedapparent" : 12.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 2.92, "navigation.speedThroughWater": 3.194700809324205, "performance.velocityMadeGood": 2.5207784163749767, "environment.wind.angleTrueWater": 0.8028514561007063, "environment.depth.belowTransducer": 5.44, "navigation.courseOverGroundMagnetic": 3.9854593478390496, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:24:28.498Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.967,
"longitude" : 23.40138333333333,
"speedoverground" : 6.1,
"courseovergroundtrue" : 197.7,
"windspeedapparent" : 10.9,
"anglespeedapparent" : 16.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 2.99, "navigation.speedThroughWater": 3.215278592315021, "performance.velocityMadeGood": 1.2964003284214165, "environment.wind.angleTrueWater": 1.274090354246773, "environment.depth.belowTransducer": 5.74, "navigation.courseOverGroundMagnetic": 3.4508649978311228, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:25:28.540Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.958866666666665,
"longitude" : 23.395816666666665,
"speedoverground" : 6.2,
"courseovergroundtrue" : 203.9,
"windspeedapparent" : 10.5,
"anglespeedapparent" : 38.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 3.44, "navigation.speedThroughWater": 3.215278592315021, "performance.velocityMadeGood": 1.512467049824986, "environment.wind.angleTrueWater": 1.2566370617228446, "environment.depth.belowTransducer": 7.13, "navigation.courseOverGroundMagnetic": 3.5587263456290006, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:26:28.572Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.9508,
"longitude" : 23.390166666666666,
"speedoverground" : 6.2,
"courseovergroundtrue" : 200.6,
"windspeedapparent" : 12.0,
"anglespeedapparent" : 63.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 4.12, "navigation.speedThroughWater": 3.2461452668012454, "performance.velocityMadeGood": -1.3787114603846813, "environment.wind.angleTrueWater": 1.884955592584267, "environment.depth.belowTransducer": 8.6, "navigation.courseOverGroundMagnetic": 3.5004323485990794, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:27:28.599Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.94275,
"longitude" : 23.38365,
"speedoverground" : 6.3,
"courseovergroundtrue" : 206.9,
"windspeedapparent" : 7.8,
"anglespeedapparent" : 22.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 2.13, "navigation.speedThroughWater": 3.2255674838104293, "performance.velocityMadeGood": -1.1317780644948876, "environment.wind.angleTrueWater": 1.9373154701560522, "environment.depth.belowTransducer": 12.98, "navigation.courseOverGroundMagnetic": 3.6112607561260246, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:28:28.606Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.93578333333333,
"longitude" : 23.37245,
"speedoverground" : 6.5,
"courseovergroundtrue" : 227.5,
"windspeedapparent" : 7.6,
"anglespeedapparent" : -10.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 1.12, "navigation.speedThroughWater": 3.2307119295581335, "performance.velocityMadeGood": -2.896322955957371, "environment.wind.angleTrueWater": -2.7750735113046154, "environment.depth.belowTransducer": 11.7, "navigation.courseOverGroundMagnetic": 3.9704495162684714, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:29:28.619Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.93076666666666,
"longitude" : 23.3572,
"speedoverground" : 6.3,
"courseovergroundtrue" : 232.5,
"windspeedapparent" : 6.8,
"anglespeedapparent" : 19.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 0.92, "navigation.speedThroughWater": 3.2307119295581335, "performance.velocityMadeGood": -1.960033829875237, "environment.wind.angleTrueWater": 2.2165681505389068, "environment.depth.belowTransducer": 14.23, "navigation.courseOverGroundMagnetic": 4.05858864351431, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:30:28.637Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.925133333333335,
"longitude" : 23.3448,
"speedoverground" : 5.9,
"courseovergroundtrue" : 228.4,
"windspeedapparent" : 7.2,
"anglespeedapparent" : 13.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 0.82, "navigation.speedThroughWater": 3.2358563753058376, "performance.velocityMadeGood": -2.1246560938017662, "environment.wind.angleTrueWater": 2.373647783254262, "environment.depth.belowTransducer": 14.29, "navigation.courseOverGroundMagnetic": 3.986855611240964, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:31:28.668Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.918933333333335,
"longitude" : 23.33415,
"speedoverground" : 6.0,
"courseovergroundtrue" : 211.6,
"windspeedapparent" : 6.4,
"anglespeedapparent" : -15.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 0.86, "navigation.speedThroughWater": 3.2255674838104293, "performance.velocityMadeGood": -0.38068898533009854, "environment.wind.angleTrueWater": -1.7278759598689115, "environment.depth.belowTransducer": 18.1, "navigation.courseOverGroundMagnetic": 3.6927676322127705, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:32:28.735Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.9114,
"longitude" : 23.32743333333333,
"speedoverground" : 6.2,
"courseovergroundtrue" : 175.6,
"windspeedapparent" : 6.4,
"anglespeedapparent" : -8.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 0.36, "navigation.speedThroughWater": 3.199845255071909, "performance.velocityMadeGood": -0.3189556363576501, "environment.wind.angleTrueWater": -1.6057029122014126, "environment.depth.belowTransducer": 10.95, "navigation.courseOverGroundMagnetic": 3.064972700127066, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:33:28.757Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.90315,
"longitude" : 23.329966666666667,
"speedoverground" : 6.1,
"courseovergroundtrue" : 144.0,
"windspeedapparent" : 5.8,
"anglespeedapparent" : -3.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 0.24, "navigation.speedThroughWater": 3.2049897008196133, "performance.velocityMadeGood": -2.4847672961410483, "environment.wind.angleTrueWater": -2.3911010757781903, "environment.depth.belowTransducer": 13.92, "navigation.courseOverGroundMagnetic": 2.512575991744732, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:34:28.787Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.89735,
"longitude" : 23.3428,
"speedoverground" : 6.3,
"courseovergroundtrue" : 129.8,
"windspeedapparent" : 5.2,
"anglespeedapparent" : -2.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 1, "navigation.speedThroughWater": 3.2307119295581335, "performance.velocityMadeGood": -3.1895563635765014, "environment.wind.angleTrueWater": -2.932153144019971, "environment.depth.belowTransducer": 11.32, "navigation.courseOverGroundMagnetic": 2.265960968381624, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:35:28.798Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.88953333333333,
"longitude" : 23.3492,
"speedoverground" : 6.5,
"courseovergroundtrue" : 162.5,
"windspeedapparent" : 7.2,
"anglespeedapparent" : -7.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 0.95, "navigation.speedThroughWater": 3.199845255071909, "performance.velocityMadeGood": -3.050656328388492, "environment.wind.angleTrueWater": -2.844886681400329, "environment.depth.belowTransducer": 10.04, "navigation.courseOverGroundMagnetic": 2.8354619034374076, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:36:28.812Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.88126666666667,
"longitude" : 23.345833333333335,
"speedoverground" : 6.1,
"courseovergroundtrue" : 196.5,
"windspeedapparent" : 8.7,
"anglespeedapparent" : -15.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 0.87, "navigation.speedThroughWater": 3.199845255071909, "performance.velocityMadeGood": -1.1832225219719277, "environment.wind.angleTrueWater": -1.972222055203909, "environment.depth.belowTransducer": 22.3, "navigation.courseOverGroundMagnetic": 3.429921046802409, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:37:28.835Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.87278333333333,
"longitude" : 23.344066666666667,
"speedoverground" : 6.2,
"courseovergroundtrue" : 160.5,
"windspeedapparent" : 7.8,
"anglespeedapparent" : -5.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 0.57, "navigation.speedThroughWater": 3.174123026333389, "performance.velocityMadeGood": 2.870600727218851, "environment.wind.angleTrueWater": -0.261799387858926, "environment.depth.belowTransducer": 24.73, "navigation.courseOverGroundMagnetic": 2.8016025159409867, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:38:28.838Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.866616666666665,
"longitude" : 23.355716666666666,
"speedoverground" : 6.1,
"courseovergroundtrue" : 131.4,
"windspeedapparent" : 35.8,
"anglespeedapparent" : 9.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 0.51, "navigation.speedThroughWater": 3.2255674838104293, "performance.velocityMadeGood": -1.347844785898457, "environment.wind.angleTrueWater": 1.7453292523928399, "environment.depth.belowTransducer": 9.85, "navigation.courseOverGroundMagnetic": 2.292489973017995, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:39:28.867Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.86,
"longitude" : 23.365766666666666,
"speedoverground" : 5.8,
"courseovergroundtrue" : 122.0,
"windspeedapparent" : 37.2,
"anglespeedapparent" : 10.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 0.63, "navigation.speedThroughWater": 3.2255674838104293, "performance.velocityMadeGood": -2.242978345998959, "environment.wind.angleTrueWater": 2.3038346131585485, "environment.depth.belowTransducer": 17.73, "navigation.courseOverGroundMagnetic": 2.129127154994025, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:40:28.867Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.86,
"longitude" : 23.365766666666666,
"speedoverground" : 4.5,
"courseovergroundtrue" : 122.0,
"windspeedapparent" : 7.2,
"anglespeedapparent" : 10.0,
"status" : "anchored",
"metrics" : {"environment.wind.speedTrue": 0.63, "navigation.speedThroughWater": 3.2255674838104293, "performance.velocityMadeGood": -2.242978345998959, "environment.wind.angleTrueWater": 2.3038346131585485, "environment.depth.belowTransducer": 17.73, "navigation.courseOverGroundMagnetic": 2.129127154994025, "navigation.courseRhumbline.crossTrackError": 0, "propulsion.main.runTime":1776262}
},
{
"time" : "2022-07-30T15:41:28.867Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.86,
"longitude" : 23.365766666666666,
"speedoverground" : 0.0,
"courseovergroundtrue" : 122.0,
"windspeedapparent" : 7.2,
"anglespeedapparent" : 10.0,
"status" : "anchored",
"metrics" : {"environment.wind.speedTrue": 0.63, "navigation.speedThroughWater": 3.2255674838104293, "performance.velocityMadeGood": -2.242978345998959, "environment.wind.angleTrueWater": 2.3038346131585485, "environment.depth.belowTransducer": 17.73, "navigation.courseOverGroundMagnetic": 2.129127154994025, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:41:28.867Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.86,
"longitude" : 23.365766666666666,
"speedoverground" : 0.0,
"courseovergroundtrue" : 122.0,
"windspeedapparent" : 7.2,
"anglespeedapparent" : 10.0,
"status" : "anchored",
"metrics" : {"environment.wind.speedTrue": 0.63, "navigation.speedThroughWater": 3.2255674838104293, "performance.velocityMadeGood": -2.242978345998959, "environment.wind.angleTrueWater": 2.3038346131585485, "environment.depth.belowTransducer": 17.73, "navigation.courseOverGroundMagnetic": 2.129127154994025, "navigation.courseRhumbline.crossTrackError": 0}
}
]}

View File

@@ -0,0 +1,879 @@
{
"metrics": [
{
"time" : "2022-12-13 20:39:04.562",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.3786471,
"longitude" : 2.277667383333333,
"speedoverground" : 4.1,
"courseovergroundtrue" : 17.4,
"windspeedapparent" : 8.1,
"anglespeedapparent" : -32.3,
"status" : "moored",
"metrics" : {"navigation.headingTrue": 0.3036872899163541, "environment.wind.speedTrue": 1.6, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.3036872899163541, "environment.depth.belowKeel": 9.3, "navigation.speedThroughWater": 2.109222756558654, "environment.water.temperature": 280.75, "environment.depth.belowSurface": 9.3, "environment.wind.directionTrue": 5.223770452411769, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 0, "propulsion.engine_2.revolutions": 0, "environment.depth.belowTransducer": 9.3, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 5.223770452411769, "navigation.gnss.horizontalDilution": 1.8, "navigation.courseOverGroundMagnetic": 0.3036872899163541, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670963929}
},
{
"time" : "2022-12-13 20:40:04.568",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.37983186666666,
"longitude" : 2.2781615333333334,
"speedoverground" : 4.1,
"courseovergroundtrue" : 16.5,
"windspeedapparent" : 6.0,
"anglespeedapparent" : -25.2,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.28797932664481857, "environment.wind.speedTrue": 1.5, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.28797932664481857, "environment.depth.belowKeel": 11.8, "navigation.speedThroughWater": 2.109222756558654, "environment.water.temperature": 280.95, "environment.depth.belowSurface": 11.8, "environment.wind.directionTrue": 5.483824511018303, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 0, "propulsion.engine_2.revolutions": 0, "environment.depth.belowTransducer": 11.8, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 5.483824511018303, "navigation.gnss.horizontalDilution": 1.7, "navigation.courseOverGroundMagnetic": 0.28797932664481857, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670963985}
},
{
"time" : "2022-12-13 20:41:04.592",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.380872266666664,
"longitude" : 2.2785093333333335,
"speedoverground" : 4.1,
"courseovergroundtrue" : 13.6,
"windspeedapparent" : 7.2,
"anglespeedapparent" : -34.7,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.2373647783254262, "environment.wind.speedTrue": 1.5, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.2373647783254262, "environment.depth.belowKeel": 10.7, "navigation.speedThroughWater": 2.109222756558654, "environment.water.temperature": 281.15, "environment.depth.belowSurface": 10.7, "environment.wind.directionTrue": 4.974188369319593, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 0, "propulsion.engine_2.revolutions": 0, "environment.depth.belowTransducer": 10.7, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 4.974188369319593, "navigation.gnss.horizontalDilution": 1.3, "navigation.courseOverGroundMagnetic": 0.2373647783254262, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670964045}
},
{
"time" : "2022-12-13 20:42:04.609",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.38208281666667,
"longitude" : 2.2788272,
"speedoverground" : 4.2,
"courseovergroundtrue" : 10.7,
"windspeedapparent" : 10.0,
"anglespeedapparent" : -39.2,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.18675023000603386, "environment.wind.speedTrue": 2.6, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.18675023000603386, "environment.depth.belowKeel": 12.4, "navigation.speedThroughWater": 2.1606672140356946, "environment.water.temperature": 281.15, "environment.depth.belowSurface": 12.4, "environment.wind.directionTrue": 5.223770452411769, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10, "propulsion.engine_2.revolutions": 10, "environment.depth.belowTransducer": 12.4, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 5.223770452411769, "navigation.gnss.horizontalDilution": 1.6, "navigation.courseOverGroundMagnetic": 0.18675023000603386, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670964109}
},
{
"time" : "2022-12-13 20:43:04.645",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.383208116666665,
"longitude" : 2.2791273833333334,
"speedoverground" : 4.1,
"courseovergroundtrue" : 11.2,
"windspeedapparent" : 6.5,
"anglespeedapparent" : -10.8,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.19547687626799803, "environment.wind.speedTrue": 1.2, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.19547687626799803, "environment.depth.belowKeel": 14.4, "navigation.speedThroughWater": 2.109222756558654, "environment.water.temperature": 280.95, "environment.depth.belowSurface": 14.4, "environment.wind.directionTrue": 5.967280713931119, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.403333333333334, "propulsion.engine_2.revolutions": 10.2, "environment.depth.belowTransducer": 14.4, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 5.967280713931119, "navigation.gnss.horizontalDilution": 1.7, "navigation.courseOverGroundMagnetic": 0.19547687626799803, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670964164}
},
{
"time" : "2022-12-13 20:44:04.683",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.38424145,
"longitude" : 2.27946345,
"speedoverground" : 4.2,
"courseovergroundtrue" : 13.6,
"windspeedapparent" : 7.0,
"anglespeedapparent" : -12.7,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.2373647783254262, "environment.wind.speedTrue": 1.5, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.2373647783254262, "environment.depth.belowKeel": 11.8, "navigation.speedThroughWater": 2.1606672140356946, "environment.water.temperature": 280.95, "environment.depth.belowSurface": 11.8, "environment.wind.directionTrue": 5.967280713931119, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.403333333333334, "propulsion.engine_2.revolutions": 10, "environment.depth.belowTransducer": 11.8, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 5.967280713931119, "navigation.gnss.horizontalDilution": 1.3, "navigation.courseOverGroundMagnetic": 0.2373647783254262, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670964225}
},
{
"time" : "2022-12-13 20:45:04.722",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.385386716666666,
"longitude" : 2.2798840166666667,
"speedoverground" : 4.2,
"courseovergroundtrue" : 15.8,
"windspeedapparent" : 5.2,
"anglespeedapparent" : -34.4,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.2757620218780687, "environment.wind.speedTrue": 1.2, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.2757620218780687, "environment.depth.belowKeel": 15.1, "navigation.speedThroughWater": 2.1606672140356946, "environment.water.temperature": 280.75, "environment.depth.belowSurface": 15.1, "environment.wind.directionTrue": 4.241150083314601, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.2, "propulsion.engine_2.revolutions": 11.04, "environment.depth.belowTransducer": 15.1, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 4.241150083314601, "navigation.gnss.horizontalDilution": 1.2, "navigation.courseOverGroundMagnetic": 0.2757620218780687, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670964290}
},
{
"time" : "2022-12-13 20:46:04.743",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.38658911666667,
"longitude" : 2.2802688166666667,
"speedoverground" : 4.6,
"courseovergroundtrue" : 13.6,
"windspeedapparent" : 2.8,
"anglespeedapparent" : 166.2,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.2373647783254262, "environment.wind.speedTrue": 3.1, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.2373647783254262, "environment.depth.belowKeel": 10.7, "navigation.speedThroughWater": 2.3664450439438554, "environment.water.temperature": 280.54999999999995, "environment.depth.belowSurface": 10.7, "environment.wind.directionTrue": 3.323106896555967, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.2, "propulsion.engine_2.revolutions": 10.403333333333334, "environment.depth.belowTransducer": 10.7, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 3.323106896555967, "navigation.gnss.horizontalDilution": 1.5, "navigation.courseOverGroundMagnetic": 0.2373647783254262, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670964355}
},
{
"time" : "2022-12-13 20:47:04.759",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.387908966666664,
"longitude" : 2.2806784166666665,
"speedoverground" : 4.6,
"courseovergroundtrue" : 13.6,
"windspeedapparent" : 8.2,
"anglespeedapparent" : 138.2,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.2373647783254262, "environment.wind.speedTrue": 4.4, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.2373647783254262, "environment.depth.belowKeel": 9.7, "navigation.speedThroughWater": 2.3664450439438554, "environment.water.temperature": 280.25, "environment.depth.belowSurface": 9.7, "environment.wind.directionTrue": 3.0141836188824342, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 11.04, "propulsion.engine_2.revolutions": 10.403333333333334, "environment.depth.belowTransducer": 9.7, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 3.0141836188824342, "navigation.gnss.horizontalDilution": 1.5, "navigation.courseOverGroundMagnetic": 0.2373647783254262, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670964414}
},
{
"time" : "2022-12-13 20:48:04.770",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.389180233333335,
"longitude" : 2.28109,
"speedoverground" : 4.7,
"courseovergroundtrue" : 15.0,
"windspeedapparent" : 3.6,
"anglespeedapparent" : 60.8,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.261799387858926, "environment.wind.speedTrue": 2.1, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.261799387858926, "environment.depth.belowKeel": 8.8, "navigation.speedThroughWater": 2.417889501420896, "environment.water.temperature": 279.84999999999997, "environment.depth.belowSurface": 8.8, "environment.wind.directionTrue": 2.733185609247187, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.2, "propulsion.engine_2.revolutions": 10.2, "environment.depth.belowTransducer": 8.8, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 2.733185609247187, "navigation.gnss.horizontalDilution": 1.7, "navigation.courseOverGroundMagnetic": 0.261799387858926, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670964474}
},
{
"time" : "2022-12-13 20:49:04.782",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.39048306666667,
"longitude" : 2.28150255,
"speedoverground" : 4.8,
"courseovergroundtrue" : 12.3,
"windspeedapparent" : 5.2,
"anglespeedapparent" : 27.3,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.21467549804431932, "environment.wind.speedTrue": 1.2, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.21467549804431932, "environment.depth.belowKeel": 7.3, "navigation.speedThroughWater": 2.4693339588979364, "environment.water.temperature": 280.04999999999995, "environment.depth.belowSurface": 7.3, "environment.wind.directionTrue": 1.8500490075364102, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 11.486666666666668, "propulsion.engine_2.revolutions": 10.611666666666668, "environment.depth.belowTransducer": 7.3, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 1.8500490075364102, "navigation.gnss.horizontalDilution": 2, "navigation.courseOverGroundMagnetic": 0.21467549804431932, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670964534}
},
{
"time" : "2022-12-13 20:50:04.811",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.3917979,
"longitude" : 2.28186645,
"speedoverground" : 4.8,
"courseovergroundtrue" : 10.2,
"windspeedapparent" : 8.3,
"anglespeedapparent" : 35.3,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.17802358374406965, "environment.wind.speedTrue": 2.6, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.17802358374406965, "environment.depth.belowKeel": 4.9, "navigation.speedThroughWater": 2.4693339588979364, "environment.water.temperature": 280.54999999999995, "environment.depth.belowSurface": 4.9, "environment.wind.directionTrue": 1.380555438642736, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 11.04, "propulsion.engine_2.revolutions": 10, "environment.depth.belowTransducer": 4.9, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 1.380555438642736, "navigation.gnss.horizontalDilution": 2.3, "navigation.courseOverGroundMagnetic": 0.17802358374406965, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670964595}
},
{
"time" : "2022-12-13 20:51:04.862",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.39308273333333,
"longitude" : 2.2821629833333335,
"speedoverground" : 5.0,
"courseovergroundtrue" : 10.2,
"windspeedapparent" : 8.6,
"anglespeedapparent" : 34.5,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.17802358374406965, "environment.wind.speedTrue": 2.6, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.17802358374406965, "environment.depth.belowKeel": 3.7, "navigation.speedThroughWater": 2.572222873852017, "environment.water.temperature": 280.65, "environment.depth.belowSurface": 3.7, "environment.wind.directionTrue": 1.380555438642736, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 11.486666666666668, "propulsion.engine_2.revolutions": 10.2, "environment.depth.belowTransducer": 3.7, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 1.380555438642736, "navigation.gnss.horizontalDilution": 2.3, "navigation.courseOverGroundMagnetic": 0.17802358374406965, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670964654}
},
{
"time" : "2022-12-13 20:52:04.904",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.394408983333335,
"longitude" : 2.2825271833333334,
"speedoverground" : 4.8,
"courseovergroundtrue" : 11.2,
"windspeedapparent" : 9.5,
"anglespeedapparent" : 46.9,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.19547687626799803, "environment.wind.speedTrue": 3.7, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.19547687626799803, "environment.depth.belowKeel": 4.5, "navigation.speedThroughWater": 2.4693339588979364, "environment.water.temperature": 280.34999999999997, "environment.depth.belowSurface": 4.5, "environment.wind.directionTrue": 1.5219271080865564, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.611666666666668, "propulsion.engine_2.revolutions": 10.403333333333334, "environment.depth.belowTransducer": 4.5, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 1.5219271080865564, "navigation.gnss.horizontalDilution": 2.3, "navigation.courseOverGroundMagnetic": 0.19547687626799803, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670964714}
},
{
"time" : "2022-12-13 20:53:04.922",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.39573106666667,
"longitude" : 2.2829267166666667,
"speedoverground" : 4.9,
"courseovergroundtrue" : 12.3,
"windspeedapparent" : 10.2,
"anglespeedapparent" : 42.4,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.21467549804431932, "environment.wind.speedTrue": 2.6, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.21467549804431932, "environment.depth.belowKeel": 4.5, "navigation.speedThroughWater": 2.5207784163749767, "environment.water.temperature": 280.65, "environment.depth.belowSurface": 4.5, "environment.wind.directionTrue": 1.679006740801912, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 11.04, "propulsion.engine_2.revolutions": 10, "environment.depth.belowTransducer": 4.5, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 1.679006740801912, "navigation.gnss.horizontalDilution": 3.4, "navigation.courseOverGroundMagnetic": 0.21467549804431932, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670964775}
},
{
"time" : "2022-12-13 20:54:04.947",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.39700681666667,
"longitude" : 2.2833958666666665,
"speedoverground" : 4.9,
"courseovergroundtrue" : 16.5,
"windspeedapparent" : 8.0,
"anglespeedapparent" : 35.7,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.28797932664481857, "environment.wind.speedTrue": 2.6, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.28797932664481857, "environment.depth.belowKeel": 4.9, "navigation.speedThroughWater": 2.5207784163749767, "environment.water.temperature": 280.65, "environment.depth.belowSurface": 4.9, "environment.wind.directionTrue": 1.5219271080865564, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.611666666666668, "propulsion.engine_2.revolutions": 10, "environment.depth.belowTransducer": 4.9, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 1.5219271080865564, "navigation.gnss.horizontalDilution": 4.4, "navigation.courseOverGroundMagnetic": 0.28797932664481857, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670964835}
},
{
"time" : "2022-12-13 20:55:04.978",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.39837505,
"longitude" : 2.283934583333333,
"speedoverground" : 4.9,
"courseovergroundtrue" : 16.5,
"windspeedapparent" : 9.4,
"anglespeedapparent" : 27.9,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.28797932664481857, "environment.wind.speedTrue": 2.6, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.28797932664481857, "environment.depth.belowKeel": 6, "navigation.speedThroughWater": 2.5207784163749767, "environment.water.temperature": 280.65, "environment.depth.belowSurface": 6, "environment.wind.directionTrue": 1.253146403218059, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.611666666666668, "propulsion.engine_2.revolutions": 10.2, "environment.depth.belowTransducer": 6, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 1.253146403218059, "navigation.gnss.horizontalDilution": 4.7, "navigation.courseOverGroundMagnetic": 0.28797932664481857, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670964895}
},
{
"time" : "2022-12-13 20:56:04.996",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.39972915,
"longitude" : 2.2844581333333336,
"speedoverground" : 5.2,
"courseovergroundtrue" : 16.5,
"windspeedapparent" : 10.3,
"anglespeedapparent" : 23.8,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.28797932664481857, "environment.wind.speedTrue": 2.6, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.28797932664481857, "environment.depth.belowKeel": 5.4, "navigation.speedThroughWater": 2.675111788806098, "environment.water.temperature": 280.84999999999997, "environment.depth.belowSurface": 5.4, "environment.wind.directionTrue": 1.1362093433077387, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 11.486666666666668, "propulsion.engine_2.revolutions": 10.2, "environment.depth.belowTransducer": 5.4, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 1.1362093433077387, "navigation.gnss.horizontalDilution": 3.8, "navigation.courseOverGroundMagnetic": 0.28797932664481857, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670964954}
},
{
"time" : "2022-12-13 20:57:05.017",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.401043683333334,
"longitude" : 2.284932,
"speedoverground" : 5.3,
"courseovergroundtrue" : 15.0,
"windspeedapparent" : 14.3,
"anglespeedapparent" : 33.6,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.261799387858926, "environment.wind.speedTrue": 5.3, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.261799387858926, "environment.depth.belowKeel": 4.9, "navigation.speedThroughWater": 2.726556246283138, "environment.water.temperature": 280.65, "environment.depth.belowSurface": 4.9, "environment.wind.directionTrue": 1.1362093433077387, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.611666666666668, "propulsion.engine_2.revolutions": 10.2, "environment.depth.belowTransducer": 4.9, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 1.1362093433077387, "navigation.gnss.horizontalDilution": 3.8, "navigation.courseOverGroundMagnetic": 0.261799387858926, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670965014}
},
{
"time" : "2022-12-13 20:58:05.043",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.40248723333333,
"longitude" : 2.2853501666666665,
"speedoverground" : 5.2,
"courseovergroundtrue" : 11.2,
"windspeedapparent" : 25.2,
"anglespeedapparent" : 36.0,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.19547687626799803, "environment.wind.speedTrue": 7.6, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.19547687626799803, "environment.depth.belowKeel": 5.4, "navigation.speedThroughWater": 2.675111788806098, "environment.water.temperature": 280.65, "environment.depth.belowSurface": 5.4, "environment.wind.directionTrue": 1.0297442589117756, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.2, "propulsion.engine_2.revolutions": 10.2, "environment.depth.belowTransducer": 5.4, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 1.0297442589117756, "navigation.gnss.horizontalDilution": 4.4, "navigation.courseOverGroundMagnetic": 0.19547687626799803, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670965075}
},
{
"time" : "2022-12-13 20:59:05.063",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.40396921666667,
"longitude" : 2.28573555,
"speedoverground" : 5.6,
"courseovergroundtrue" : 10.2,
"windspeedapparent" : 14.6,
"anglespeedapparent" : 27.6,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.17802358374406965, "environment.wind.speedTrue": 3.7, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.17802358374406965, "environment.depth.belowKeel": 5.4, "navigation.speedThroughWater": 2.8808896187142587, "environment.water.temperature": 280.65, "environment.depth.belowSurface": 5.4, "environment.wind.directionTrue": 1.0297442589117756, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.825, "propulsion.engine_2.revolutions": 10.611666666666668, "environment.depth.belowTransducer": 5.4, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 1.0297442589117756, "navigation.gnss.horizontalDilution": 4.4, "navigation.courseOverGroundMagnetic": 0.17802358374406965, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670965135}
},
{
"time" : "2022-12-13 21:00:05.096",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.40549068333333,
"longitude" : 2.2861892666666668,
"speedoverground" : 5.5,
"courseovergroundtrue" : 13.6,
"windspeedapparent" : 15.4,
"anglespeedapparent" : 20.0,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.2373647783254262, "environment.wind.speedTrue": 5.3, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.2373647783254262, "environment.depth.belowKeel": 4.9, "navigation.speedThroughWater": 2.829445161237219, "environment.water.temperature": 280.65, "environment.depth.belowSurface": 4.9, "environment.wind.directionTrue": 0.7696902003052424, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.403333333333334, "propulsion.engine_2.revolutions": 10.825, "environment.depth.belowTransducer": 4.9, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.7696902003052424, "navigation.gnss.horizontalDilution": 4.7, "navigation.courseOverGroundMagnetic": 0.2373647783254262, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670965195}
},
{
"time" : "2022-12-13 21:01:05.130",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.406966833333335,
"longitude" : 2.2866105,
"speedoverground" : 5.5,
"courseovergroundtrue" : 10.2,
"windspeedapparent" : 19.4,
"anglespeedapparent" : 24.7,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.17802358374406965, "environment.wind.speedTrue": 3.7, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.17802358374406965, "environment.depth.belowKeel": 4.5, "navigation.speedThroughWater": 2.829445161237219, "environment.water.temperature": 280.54999999999995, "environment.depth.belowSurface": 4.5, "environment.wind.directionTrue": 0.9337511500301693, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10, "propulsion.engine_2.revolutions": 12.19, "environment.depth.belowTransducer": 4.5, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.9337511500301693, "navigation.gnss.horizontalDilution": 5, "navigation.courseOverGroundMagnetic": 0.17802358374406965, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670965255}
},
{
"time" : "2022-12-13 21:02:05.156",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.40843818333333,
"longitude" : 2.2869834833333336,
"speedoverground" : 5.3,
"courseovergroundtrue" : 10.2,
"windspeedapparent" : 14.6,
"anglespeedapparent" : 29.0,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.17802358374406965, "environment.wind.speedTrue": 5.3, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.17802358374406965, "environment.depth.belowKeel": 4.9, "navigation.speedThroughWater": 2.726556246283138, "environment.water.temperature": 280.25, "environment.depth.belowSurface": 4.9, "environment.wind.directionTrue": 0.9337511500301693, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.825, "propulsion.engine_2.revolutions": 11.716666666666667, "environment.depth.belowTransducer": 4.9, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.9337511500301693, "navigation.gnss.horizontalDilution": 3.3, "navigation.courseOverGroundMagnetic": 0.17802358374406965, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670965315}
},
{
"time" : "2022-12-13 21:03:05.202",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.40989965,
"longitude" : 2.287289433333333,
"speedoverground" : 5.4,
"courseovergroundtrue" : 9.2,
"windspeedapparent" : 14.4,
"anglespeedapparent" : 26.7,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.16057029122014124, "environment.wind.speedTrue": 2.6, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.16057029122014124, "environment.depth.belowKeel": 6, "navigation.speedThroughWater": 2.7780007037601786, "environment.water.temperature": 280.25, "environment.depth.belowSurface": 6, "environment.wind.directionTrue": 1.1362093433077387, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.403333333333334, "propulsion.engine_2.revolutions": 11.261666666666667, "environment.depth.belowTransducer": 6, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 1.1362093433077387, "navigation.gnss.horizontalDilution": 3.8, "navigation.courseOverGroundMagnetic": 0.16057029122014124, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670965375}
},
{
"time" : "2022-12-13 21:04:05.224",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.411323816666666,
"longitude" : 2.287555033333333,
"speedoverground" : 5.1,
"courseovergroundtrue" : 7.6,
"windspeedapparent" : 9.3,
"anglespeedapparent" : 22.7,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.1326450231818558, "environment.wind.speedTrue": 2.6, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.1326450231818558, "environment.depth.belowKeel": 5.4, "navigation.speedThroughWater": 2.6236673313290573, "environment.water.temperature": 279.95, "environment.depth.belowSurface": 5.4, "environment.wind.directionTrue": 0.9337511500301693, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 11.261666666666667, "propulsion.engine_2.revolutions": 10.825, "environment.depth.belowTransducer": 5.4, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.9337511500301693, "navigation.gnss.horizontalDilution": 3.3, "navigation.courseOverGroundMagnetic": 0.1326450231818558, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670965435}
},
{
"time" : "2022-12-13 21:05:05.253",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.412672,
"longitude" : 2.2878124166666667,
"speedoverground" : 4.7,
"courseovergroundtrue" : 8.4,
"windspeedapparent" : 16.5,
"anglespeedapparent" : 24.7,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.14660765720099855, "environment.wind.speedTrue": 5.3, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.14660765720099855, "environment.depth.belowKeel": 4.5, "navigation.speedThroughWater": 2.417889501420896, "environment.water.temperature": 279.65, "environment.depth.belowSurface": 4.5, "environment.wind.directionTrue": 0.7696902003052424, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 11.261666666666667, "propulsion.engine_2.revolutions": 10.825, "environment.depth.belowTransducer": 4.5, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.7696902003052424, "navigation.gnss.horizontalDilution": 5, "navigation.courseOverGroundMagnetic": 0.14660765720099855, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670965495}
},
{
"time" : "2022-12-13 21:06:05.281",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.413960216666666,
"longitude" : 2.288052583333333,
"speedoverground" : 4.8,
"courseovergroundtrue" : 7.6,
"windspeedapparent" : 19.1,
"anglespeedapparent" : 24.7,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.1326450231818558, "environment.wind.speedTrue": 7.6, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.1326450231818558, "environment.depth.belowKeel": 3.3, "navigation.speedThroughWater": 2.4693339588979364, "environment.water.temperature": 279.65, "environment.depth.belowSurface": 3.3, "environment.wind.directionTrue": 0.698131700957136, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 11.716666666666667, "propulsion.engine_2.revolutions": 11.716666666666667, "environment.depth.belowTransducer": 3.3, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.698131700957136, "navigation.gnss.horizontalDilution": 3.1, "navigation.courseOverGroundMagnetic": 0.1326450231818558, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670965555}
},
{
"time" : "2022-12-13 21:07:05.315",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.41527805,
"longitude" : 2.288307083333333,
"speedoverground" : 4.8,
"courseovergroundtrue" : 7.6,
"windspeedapparent" : 22.1,
"anglespeedapparent" : 22.3,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.1326450231818558, "environment.wind.speedTrue": 5.3, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.1326450231818558, "environment.depth.belowKeel": 4, "navigation.speedThroughWater": 2.4693339588979364, "environment.water.temperature": 279.54999999999995, "environment.depth.belowSurface": 4, "environment.wind.directionTrue": 0.698131700957136, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 11.261666666666667, "propulsion.engine_2.revolutions": 12.681666666666667, "environment.depth.belowTransducer": 4, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.698131700957136, "navigation.gnss.horizontalDilution": 2.7, "navigation.courseOverGroundMagnetic": 0.1326450231818558, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670965615}
},
{
"time" : "2022-12-13 21:08:05.353",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.41658073333333,
"longitude" : 2.2885240166666665,
"speedoverground" : 4.7,
"courseovergroundtrue" : 6.9,
"windspeedapparent" : 16.6,
"anglespeedapparent" : 12.4,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.12042771841510595, "environment.wind.speedTrue": 1.8, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.12042771841510595, "environment.depth.belowKeel": 4.9, "navigation.speedThroughWater": 2.417889501420896, "environment.water.temperature": 279.45, "environment.depth.belowSurface": 4.9, "environment.wind.directionTrue": 0.6318091893662081, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 11.716666666666667, "propulsion.engine_2.revolutions": 14.860000000000001, "environment.depth.belowTransducer": 4.9, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.6318091893662081, "navigation.gnss.horizontalDilution": 3.1, "navigation.courseOverGroundMagnetic": 0.12042771841510595, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670965675}
},
{
"time" : "2022-12-13 21:09:05.371",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.41785333333333,
"longitude" : 2.2887258333333333,
"speedoverground" : 4.5,
"courseovergroundtrue" : 6.2,
"windspeedapparent" : 8.6,
"anglespeedapparent" : 9.2,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.10821041364835607, "environment.wind.speedTrue": 1.2, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.10821041364835607, "environment.depth.belowKeel": 6, "navigation.speedThroughWater": 2.3150005864668155, "environment.water.temperature": 279.25, "environment.depth.belowSurface": 6, "environment.wind.directionTrue": 0.5742133240372442, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.403333333333334, "propulsion.engine_2.revolutions": 13.195, "environment.depth.belowTransducer": 6, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.5742133240372442, "navigation.gnss.horizontalDilution": 2.7, "navigation.courseOverGroundMagnetic": 0.10821041364835607, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670965735}
},
{
"time" : "2022-12-13 21:10:05.404",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.41911275,
"longitude" : 2.28890385,
"speedoverground" : 4.6,
"courseovergroundtrue" : 6.9,
"windspeedapparent" : 7.2,
"anglespeedapparent" : 7.8,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.12042771841510595, "environment.wind.speedTrue": 1, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.12042771841510595, "environment.depth.belowKeel": 7.3, "navigation.speedThroughWater": 2.3664450439438554, "environment.water.temperature": 279.25, "environment.depth.belowSurface": 7.3, "environment.wind.directionTrue": 0.5742133240372442, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.611666666666668, "propulsion.engine_2.revolutions": 12.681666666666667, "environment.depth.belowTransducer": 7.3, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.5742133240372442, "navigation.gnss.horizontalDilution": 2.7, "navigation.courseOverGroundMagnetic": 0.12042771841510595, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670965795}
},
{
"time" : "2022-12-13 21:11:05.414",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.42043938333333,
"longitude" : 2.2891021,
"speedoverground" : 4.8,
"courseovergroundtrue" : 6.9,
"windspeedapparent" : 7.5,
"anglespeedapparent" : 9.6,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.12042771841510595, "environment.wind.speedTrue": 1, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.12042771841510595, "environment.depth.belowKeel": 7.3, "navigation.speedThroughWater": 2.4693339588979364, "environment.water.temperature": 279.54999999999995, "environment.depth.belowSurface": 7.3, "environment.wind.directionTrue": 0.698131700957136, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 11.04, "propulsion.engine_2.revolutions": 12.681666666666667, "environment.depth.belowTransducer": 7.3, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.698131700957136, "navigation.gnss.horizontalDilution": 4.1, "navigation.courseOverGroundMagnetic": 0.12042771841510595, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670965855}
},
{
"time" : "2022-12-13 21:12:05.470",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.42173171666666,
"longitude" : 2.2892923166666668,
"speedoverground" : 4.5,
"courseovergroundtrue" : 5.7,
"windspeedapparent" : 7.3,
"anglespeedapparent" : 14.8,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.09948376738639188, "environment.wind.speedTrue": 1.5, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.09948376738639188, "environment.depth.belowKeel": 7.3, "navigation.speedThroughWater": 2.3150005864668155, "environment.water.temperature": 279.54999999999995, "environment.depth.belowSurface": 7.3, "environment.wind.directionTrue": 0.7696902003052424, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 11.04, "propulsion.engine_2.revolutions": 12.681666666666667, "environment.depth.belowTransducer": 7.3, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.7696902003052424, "navigation.gnss.horizontalDilution": 5, "navigation.courseOverGroundMagnetic": 0.09948376738639188, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670965915}
},
{
"time" : "2022-12-13 21:13:05.493",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.42296415,
"longitude" : 2.28942895,
"speedoverground" : 4.4,
"courseovergroundtrue" : 4.2,
"windspeedapparent" : 6.9,
"anglespeedapparent" : 12.4,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.07330382860049928, "environment.wind.speedTrue": 1.2, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.07330382860049928, "environment.depth.belowKeel": 11.8, "navigation.speedThroughWater": 2.263556128989775, "environment.water.temperature": 279.84999999999997, "environment.depth.belowSurface": 11.8, "environment.wind.directionTrue": 0.698131700957136, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.2, "propulsion.engine_2.revolutions": 11.716666666666667, "environment.depth.belowTransducer": 11.8, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.698131700957136, "navigation.gnss.horizontalDilution": 4.1, "navigation.courseOverGroundMagnetic": 0.07330382860049928, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670965975}
},
{
"time" : "2022-12-13 21:14:05.507",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.42416923333333,
"longitude" : 2.2895500166666665,
"speedoverground" : 4.4,
"courseovergroundtrue" : 4.7,
"windspeedapparent" : 11.3,
"anglespeedapparent" : 13.3,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.08203047486246347, "environment.wind.speedTrue": 2.6, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.08203047486246347, "environment.depth.belowKeel": 13, "navigation.speedThroughWater": 2.263556128989775, "environment.water.temperature": 280.04999999999995, "environment.depth.belowSurface": 13, "environment.wind.directionTrue": 0.5201081172130663, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.611666666666668, "propulsion.engine_2.revolutions": 11.261666666666667, "environment.depth.belowTransducer": 13, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.5201081172130663, "navigation.gnss.horizontalDilution": 4.7, "navigation.courseOverGroundMagnetic": 0.08203047486246347, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670966035}
},
{
"time" : "2022-12-13 21:15:05.532",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.42538771666667,
"longitude" : 2.2896824166666665,
"speedoverground" : 4.5,
"courseovergroundtrue" : 4.7,
"windspeedapparent" : 9.1,
"anglespeedapparent" : 18.6,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.08203047486246347, "environment.wind.speedTrue": 2.6, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.08203047486246347, "environment.depth.belowKeel": 14.4, "navigation.speedThroughWater": 2.3150005864668155, "environment.water.temperature": 279.84999999999997, "environment.depth.belowSurface": 14.4, "environment.wind.directionTrue": 0.698131700957136, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.611666666666668, "propulsion.engine_2.revolutions": 12.19, "environment.depth.belowTransducer": 14.4, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.698131700957136, "navigation.gnss.horizontalDilution": 3.6, "navigation.courseOverGroundMagnetic": 0.08203047486246347, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670966095}
},
{
"time" : "2022-12-13 21:16:05.578",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.42659741666667,
"longitude" : 2.2898227,
"speedoverground" : 4.4,
"courseovergroundtrue" : 4.7,
"windspeedapparent" : 11.2,
"anglespeedapparent" : 19.6,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.08203047486246347, "environment.wind.speedTrue": 3.7, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.08203047486246347, "environment.depth.belowKeel": 13, "navigation.speedThroughWater": 2.263556128989775, "environment.water.temperature": 279.45, "environment.depth.belowSurface": 13, "environment.wind.directionTrue": 0.6318091893662081, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 11.04, "propulsion.engine_2.revolutions": 12.681666666666667, "environment.depth.belowTransducer": 13, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.6318091893662081, "navigation.gnss.horizontalDilution": 5, "navigation.courseOverGroundMagnetic": 0.08203047486246347, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670966155}
},
{
"time" : "2022-12-13 21:17:05.597",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.42780106666667,
"longitude" : 2.2899329666666666,
"speedoverground" : 4.3,
"courseovergroundtrue" : 4.2,
"windspeedapparent" : 12.5,
"anglespeedapparent" : 15.5,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.07330382860049928, "environment.wind.speedTrue": 1.2, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.07330382860049928, "environment.depth.belowKeel": 14.4, "navigation.speedThroughWater": 2.2121116715127345, "environment.water.temperature": 279.95, "environment.depth.belowSurface": 14.4, "environment.wind.directionTrue": 0.8482300166629202, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.611666666666668, "propulsion.engine_2.revolutions": 12.19, "environment.depth.belowTransducer": 14.4, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.8482300166629202, "navigation.gnss.horizontalDilution": 2.7, "navigation.courseOverGroundMagnetic": 0.07330382860049928, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670966215}
},
{
"time" : "2022-12-13 21:18:05.608",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.42899953333333,
"longitude" : 2.2900571666666667,
"speedoverground" : 4.3,
"courseovergroundtrue" : 4.2,
"windspeedapparent" : 9.2,
"anglespeedapparent" : 32.1,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.07330382860049928, "environment.wind.speedTrue": 3.1, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.07330382860049928, "environment.depth.belowKeel": 15.8, "navigation.speedThroughWater": 2.2121116715127345, "environment.water.temperature": 279.84999999999997, "environment.depth.belowSurface": 15.8, "environment.wind.directionTrue": 1.0297442589117756, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.611666666666668, "propulsion.engine_2.revolutions": 11.716666666666667, "environment.depth.belowTransducer": 15.8, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 1.0297442589117756, "navigation.gnss.horizontalDilution": 3.1, "navigation.courseOverGroundMagnetic": 0.07330382860049928, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670966275}
},
{
"time" : "2022-12-13 21:19:05.630",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.43017308333334,
"longitude" : 2.290175666666667,
"speedoverground" : 4.2,
"courseovergroundtrue" : 4.7,
"windspeedapparent" : 12.2,
"anglespeedapparent" : 26.6,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.08203047486246347, "environment.wind.speedTrue": 4.4, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.08203047486246347, "environment.depth.belowKeel": 11.8, "navigation.speedThroughWater": 2.1606672140356946, "environment.water.temperature": 279.95, "environment.depth.belowSurface": 11.8, "environment.wind.directionTrue": 0.7696902003052424, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.611666666666668, "propulsion.engine_2.revolutions": 13.728333333333333, "environment.depth.belowTransducer": 11.8, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.7696902003052424, "navigation.gnss.horizontalDilution": 3.6, "navigation.courseOverGroundMagnetic": 0.08203047486246347, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670966335}
},
{
"time" : "2022-12-13 21:20:05.651",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.431364,
"longitude" : 2.2902968333333336,
"speedoverground" : 4.2,
"courseovergroundtrue" : 4.7,
"windspeedapparent" : 9.9,
"anglespeedapparent" : 14.2,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.08203047486246347, "environment.wind.speedTrue": 1.5, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.08203047486246347, "environment.depth.belowKeel": 9.7, "navigation.speedThroughWater": 2.1606672140356946, "environment.water.temperature": 279.95, "environment.depth.belowSurface": 9.7, "environment.wind.directionTrue": 0.698131700957136, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.611666666666668, "propulsion.engine_2.revolutions": 13.195, "environment.depth.belowTransducer": 9.7, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.698131700957136, "navigation.gnss.horizontalDilution": 3.6, "navigation.courseOverGroundMagnetic": 0.08203047486246347, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670966395}
},
{
"time" : "2022-12-13 21:21:05.683",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.43257,
"longitude" : 2.2904335666666666,
"speedoverground" : 4.4,
"courseovergroundtrue" : 4.7,
"windspeedapparent" : 8.1,
"anglespeedapparent" : 19.1,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.08203047486246347, "environment.wind.speedTrue": 2.1, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.08203047486246347, "environment.depth.belowKeel": 11.8, "navigation.speedThroughWater": 2.263556128989775, "environment.water.temperature": 279.65, "environment.depth.belowSurface": 11.8, "environment.wind.directionTrue": 0.7696902003052424, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 11.486666666666668, "propulsion.engine_2.revolutions": 12.681666666666667, "environment.depth.belowTransducer": 11.8, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.7696902003052424, "navigation.gnss.horizontalDilution": 2.4, "navigation.courseOverGroundMagnetic": 0.08203047486246347, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670966455}
},
{
"time" : "2022-12-13 21:22:05.702",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.4338153,
"longitude" : 2.2905634333333333,
"speedoverground" : 4.4,
"courseovergroundtrue" : 4.2,
"windspeedapparent" : 7.6,
"anglespeedapparent" : 10.9,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.07330382860049928, "environment.wind.speedTrue": 1, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.07330382860049928, "environment.depth.belowKeel": 13, "navigation.speedThroughWater": 2.263556128989775, "environment.water.temperature": 279.45, "environment.depth.belowSurface": 13, "environment.wind.directionTrue": 0.698131700957136, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 11.04, "propulsion.engine_2.revolutions": 13.195, "environment.depth.belowTransducer": 13, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.698131700957136, "navigation.gnss.horizontalDilution": 1.8, "navigation.courseOverGroundMagnetic": 0.07330382860049928, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670966515}
},
{
"time" : "2022-12-13 21:23:05.714",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.43504795,
"longitude" : 2.2906984,
"speedoverground" : 4.5,
"courseovergroundtrue" : 4.7,
"windspeedapparent" : 8.0,
"anglespeedapparent" : 21.0,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.08203047486246347, "environment.wind.speedTrue": 2.1, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.08203047486246347, "environment.depth.belowKeel": 13, "navigation.speedThroughWater": 2.3150005864668155, "environment.water.temperature": 279.65, "environment.depth.belowSurface": 13, "environment.wind.directionTrue": 0.8482300166629202, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.2, "propulsion.engine_2.revolutions": 13.728333333333333, "environment.depth.belowTransducer": 13, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.8482300166629202, "navigation.gnss.horizontalDilution": 1.6, "navigation.courseOverGroundMagnetic": 0.08203047486246347, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670966575}
},
{
"time" : "2022-12-13 21:24:05.728",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.436295183333336,
"longitude" : 2.2908305666666666,
"speedoverground" : 4.5,
"courseovergroundtrue" : 4.2,
"windspeedapparent" : 7.9,
"anglespeedapparent" : 20.9,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.07330382860049928, "environment.wind.speedTrue": 1.5, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.07330382860049928, "environment.depth.belowKeel": 11.8, "navigation.speedThroughWater": 2.3150005864668155, "environment.water.temperature": 279.54999999999995, "environment.depth.belowSurface": 11.8, "environment.wind.directionTrue": 1.0297442589117756, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.2, "propulsion.engine_2.revolutions": 14.281666666666666, "environment.depth.belowTransducer": 11.8, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 1.0297442589117756, "navigation.gnss.horizontalDilution": 1.4, "navigation.courseOverGroundMagnetic": 0.07330382860049928, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670966635}
},
{
"time" : "2022-12-13 21:25:05.748",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.437534766666666,
"longitude" : 2.2909366166666665,
"speedoverground" : 4.5,
"courseovergroundtrue" : 3.1,
"windspeedapparent" : 7.9,
"anglespeedapparent" : 14.2,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.054105206824178034, "environment.wind.speedTrue": 1.5, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.054105206824178034, "environment.depth.belowKeel": 11.8, "navigation.speedThroughWater": 2.3150005864668155, "environment.water.temperature": 279.84999999999997, "environment.depth.belowSurface": 11.8, "environment.wind.directionTrue": 0.698131700957136, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10, "propulsion.engine_2.revolutions": 13.195, "environment.depth.belowTransducer": 11.8, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.698131700957136, "navigation.gnss.horizontalDilution": 0.9, "navigation.courseOverGroundMagnetic": 0.054105206824178034, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670966695}
},
{
"time" : "2022-12-13 21:26:05.787",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.43881773333333,
"longitude" : 2.2910521833333335,
"speedoverground" : 4.6,
"courseovergroundtrue" : 4.2,
"windspeedapparent" : 8.5,
"anglespeedapparent" : 11.0,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.07330382860049928, "environment.wind.speedTrue": 1.5, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.07330382860049928, "environment.depth.belowKeel": 17.5, "navigation.speedThroughWater": 2.3664450439438554, "environment.water.temperature": 279.45, "environment.depth.belowSurface": 17.5, "environment.wind.directionTrue": 0.5742133240372442, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.825, "propulsion.engine_2.revolutions": 13.728333333333333, "environment.depth.belowTransducer": 17.5, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.5742133240372442, "navigation.gnss.horizontalDilution": 1.4, "navigation.courseOverGroundMagnetic": 0.07330382860049928, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670966755}
},
{
"time" : "2022-12-13 21:27:05.827",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.4401189,
"longitude" : 2.2911686833333333,
"speedoverground" : 4.7,
"courseovergroundtrue" : 4.2,
"windspeedapparent" : 7.0,
"anglespeedapparent" : 7.7,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.07330382860049928, "environment.wind.speedTrue": 1.2, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.07330382860049928, "environment.depth.belowKeel": 23.4, "navigation.speedThroughWater": 2.417889501420896, "environment.water.temperature": 279.54999999999995, "environment.depth.belowSurface": 23.4, "environment.wind.directionTrue": 0.47123889814606673, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10, "propulsion.engine_2.revolutions": 14.281666666666666, "environment.depth.belowTransducer": 23.4, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.47123889814606673, "navigation.gnss.horizontalDilution": 1.2, "navigation.courseOverGroundMagnetic": 0.07330382860049928, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670966815}
},
{
"time" : "2022-12-13 21:28:05.867",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.441560683333336,
"longitude" : 2.291303533333333,
"speedoverground" : 4.9,
"courseovergroundtrue" : 4.2,
"windspeedapparent" : 7.5,
"anglespeedapparent" : 11.8,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.07330382860049928, "environment.wind.speedTrue": 1.5, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.07330382860049928, "environment.depth.belowKeel": 28.4, "navigation.speedThroughWater": 2.5207784163749767, "environment.water.temperature": 279.45, "environment.depth.belowSurface": 28.4, "environment.wind.directionTrue": 0.6318091893662081, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.403333333333334, "propulsion.engine_2.revolutions": 16.085, "environment.depth.belowTransducer": 28.4, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.6318091893662081, "navigation.gnss.horizontalDilution": 1.1, "navigation.courseOverGroundMagnetic": 0.07330382860049928, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670966875}
},
{
"time" : "2022-12-13 21:29:05.889",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.44287495,
"longitude" : 2.291422783333333,
"speedoverground" : 4.7,
"courseovergroundtrue" : 3.8,
"windspeedapparent" : 10.4,
"anglespeedapparent" : 12.2,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.0663225115909279, "environment.wind.speedTrue": 1.5, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.0663225115909279, "environment.depth.belowKeel": 28.4, "navigation.speedThroughWater": 2.417889501420896, "environment.water.temperature": 279.54999999999995, "environment.depth.belowSurface": 28.4, "environment.wind.directionTrue": 0.6318091893662081, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.825, "propulsion.engine_2.revolutions": 18.113333333333333, "environment.depth.belowTransducer": 28.4, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.6318091893662081, "navigation.gnss.horizontalDilution": 1.2, "navigation.courseOverGroundMagnetic": 0.0663225115909279, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670966935}
},
{
"time" : "2022-12-13 21:30:05.922",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.44416415,
"longitude" : 2.2915161166666667,
"speedoverground" : 4.7,
"courseovergroundtrue" : 2.9,
"windspeedapparent" : 10.1,
"anglespeedapparent" : 20.9,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.050614548319392355, "environment.wind.speedTrue": 3.1, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.050614548319392355, "environment.depth.belowKeel": 23.4, "navigation.speedThroughWater": 2.417889501420896, "environment.water.temperature": 279.45, "environment.depth.belowSurface": 23.4, "environment.wind.directionTrue": 0.698131700957136, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.825, "propulsion.engine_2.revolutions": 15.46, "environment.depth.belowTransducer": 23.4, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.698131700957136, "navigation.gnss.horizontalDilution": 1.4, "navigation.courseOverGroundMagnetic": 0.050614548319392355, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670966995}
},
{
"time" : "2022-12-13 21:31:05.937",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.44544285,
"longitude" : 2.2916172833333333,
"speedoverground" : 4.6,
"courseovergroundtrue" : 3.5,
"windspeedapparent" : 25.5,
"anglespeedapparent" : 23.5,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.06108652383374939, "environment.wind.speedTrue": 9.2, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.06108652383374939, "environment.depth.belowKeel": 19.3, "navigation.speedThroughWater": 2.3664450439438554, "environment.water.temperature": 279.25, "environment.depth.belowSurface": 19.3, "environment.wind.directionTrue": 0.5742133240372442, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.825, "propulsion.engine_2.revolutions": 16.085, "environment.depth.belowTransducer": 19.3, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.5742133240372442, "navigation.gnss.horizontalDilution": 1.6, "navigation.courseOverGroundMagnetic": 0.06108652383374939, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670967055}
},
{
"time" : "2022-12-13 21:32:05.953",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.44671025,
"longitude" : 2.2917142166666666,
"speedoverground" : 4.6,
"courseovergroundtrue" : 3.1,
"windspeedapparent" : 22.0,
"anglespeedapparent" : 17.4,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.054105206824178034, "environment.wind.speedTrue": 4.4, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.054105206824178034, "environment.depth.belowKeel": 21.2, "navigation.speedThroughWater": 2.3664450439438554, "environment.water.temperature": 279.54999999999995, "environment.depth.belowSurface": 21.2, "environment.wind.directionTrue": 0.5201081172130663, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.825, "propulsion.engine_2.revolutions": 16.735, "environment.depth.belowTransducer": 21.2, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.5201081172130663, "navigation.gnss.horizontalDilution": 1.4, "navigation.courseOverGroundMagnetic": 0.054105206824178034, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670967114}
},
{
"time" : "2022-12-13 21:33:05.972",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.447963533333336,
"longitude" : 2.2918140833333336,
"speedoverground" : 4.4,
"courseovergroundtrue" : 3.5,
"windspeedapparent" : 22.0,
"anglespeedapparent" : 21.8,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.06108652383374939, "environment.wind.speedTrue": 6.4, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.06108652383374939, "environment.depth.belowKeel": 23.4, "navigation.speedThroughWater": 2.263556128989775, "environment.water.temperature": 279.25, "environment.depth.belowSurface": 23.4, "environment.wind.directionTrue": 0.5742133240372442, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.825, "propulsion.engine_2.revolutions": 16.085, "environment.depth.belowTransducer": 23.4, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.5742133240372442, "navigation.gnss.horizontalDilution": 1.6, "navigation.courseOverGroundMagnetic": 0.06108652383374939, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670967175}
},
{
"time" : "2022-12-13 21:34:05.994",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.44916808333333,
"longitude" : 2.2919039666666667,
"speedoverground" : 4.3,
"courseovergroundtrue" : 3.1,
"windspeedapparent" : 21.7,
"anglespeedapparent" : 24.7,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.054105206824178034, "environment.wind.speedTrue": 6.4, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.054105206824178034, "environment.depth.belowKeel": 23.4, "navigation.speedThroughWater": 2.2121116715127345, "environment.water.temperature": 279.04999999999995, "environment.depth.belowSurface": 23.4, "environment.wind.directionTrue": 0.6318091893662081, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 11.261666666666667, "propulsion.engine_2.revolutions": 18.845000000000002, "environment.depth.belowTransducer": 23.4, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.6318091893662081, "navigation.gnss.horizontalDilution": 1.6, "navigation.courseOverGroundMagnetic": 0.054105206824178034, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670967235}
},
{
"time" : "2022-12-13 21:35:06.024",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.45040101666667,
"longitude" : 2.2919925833333332,
"speedoverground" : 4.5,
"courseovergroundtrue" : 3.5,
"windspeedapparent" : 14.3,
"anglespeedapparent" : 24.1,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.06108652383374939, "environment.wind.speedTrue": 4.4, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.06108652383374939, "environment.depth.belowKeel": 21.2, "navigation.speedThroughWater": 2.3150005864668155, "environment.water.temperature": 278.95, "environment.depth.belowSurface": 21.2, "environment.wind.directionTrue": 0.698131700957136, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10, "propulsion.engine_2.revolutions": 20.398333333333333, "environment.depth.belowTransducer": 21.2, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.698131700957136, "navigation.gnss.horizontalDilution": 1.8, "navigation.courseOverGroundMagnetic": 0.06108652383374939, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670967295}
},
{
"time" : "2022-12-13 21:36:06.057",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.451662666666664,
"longitude" : 2.2920915833333333,
"speedoverground" : 4.6,
"courseovergroundtrue" : 3.1,
"windspeedapparent" : 29.1,
"anglespeedapparent" : 39.0,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.054105206824178034, "environment.wind.speedTrue": 13.2, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.054105206824178034, "environment.depth.belowKeel": 19.3, "navigation.speedThroughWater": 2.3664450439438554, "environment.water.temperature": 279.04999999999995, "environment.depth.belowSurface": 19.3, "environment.wind.directionTrue": 0.8482300166629202, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.611666666666668, "propulsion.engine_2.revolutions": 19.60666666666667, "environment.depth.belowTransducer": 19.3, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.8482300166629202, "navigation.gnss.horizontalDilution": 1.8, "navigation.courseOverGroundMagnetic": 0.054105206824178034, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670967355}
},
{
"time" : "2022-12-13 21:37:06.080",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.45293165,
"longitude" : 2.2921954833333333,
"speedoverground" : 4.7,
"courseovergroundtrue" : 3.8,
"windspeedapparent" : 44.2,
"anglespeedapparent" : 26.1,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.0663225115909279, "environment.wind.speedTrue": 20.6, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.0663225115909279, "environment.depth.belowKeel": 17.5, "navigation.speedThroughWater": 2.417889501420896, "environment.water.temperature": 279.45, "environment.depth.belowSurface": 17.5, "environment.wind.directionTrue": 0.5742133240372442, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 11.04, "propulsion.engine_2.revolutions": 22.08, "environment.depth.belowTransducer": 17.5, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.5742133240372442, "navigation.gnss.horizontalDilution": 2.1, "navigation.courseOverGroundMagnetic": 0.0663225115909279, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670967415}
},
{
"time" : "2022-12-13 21:38:06.100",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.45424523333333,
"longitude" : 2.2922960833333335,
"speedoverground" : 4.8,
"courseovergroundtrue" : 3.1,
"windspeedapparent" : 37.6,
"anglespeedapparent" : 14.2,
"status" : "motoring",
"metrics" : {"navigation.headingTrue": 0.054105206824178034, "environment.wind.speedTrue": 4.8, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.054105206824178034, "environment.depth.belowKeel": 15.8, "navigation.speedThroughWater": 2.4693339588979364, "environment.water.temperature": 279.45, "environment.depth.belowSurface": 15.8, "environment.wind.directionTrue": 0.42760566683624573, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 11.04, "propulsion.engine_2.revolutions": 23.9, "environment.depth.belowTransducer": 15.8, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.42760566683624573, "navigation.gnss.horizontalDilution": 1.6, "navigation.courseOverGroundMagnetic": 0.054105206824178034, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670967475}
},
{
"time" : "2022-12-13 21:39:06.118",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.4555779,
"longitude" : 2.292398116666667,
"speedoverground" : 4.8,
"courseovergroundtrue" : 3.5,
"windspeedapparent" : 15.7,
"anglespeedapparent" : 10.8,
"status" : "motoring",
"metrics" : {"navigation.headingTrue": 0.06108652383374939, "environment.wind.speedTrue": 3.3, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.06108652383374939, "environment.depth.belowKeel": 15.8, "navigation.speedThroughWater": 2.4693339588979364, "environment.water.temperature": 279.45, "environment.depth.belowSurface": 15.8, "environment.wind.directionTrue": 0.38746309403121043, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.2, "propulsion.engine_2.revolutions": 22.08, "environment.depth.belowTransducer": 15.8, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.38746309403121043, "navigation.gnss.horizontalDilution": 1.4, "navigation.courseOverGroundMagnetic": 0.06108652383374939, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670967535}
},
{
"time" : "2022-12-13 21:40:06.142",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.45691458333334,
"longitude" : 2.2925072833333333,
"speedoverground" : 4.9,
"courseovergroundtrue" : 3.1,
"windspeedapparent" : 15.7,
"anglespeedapparent" : 15.7,
"status" : "motoring",
"metrics" : {"navigation.headingTrue": 0.054105206824178034, "environment.wind.speedTrue": 4.8, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.054105206824178034, "environment.depth.belowKeel": 15.8, "navigation.speedThroughWater": 2.5207784163749767, "environment.water.temperature": 279.65, "environment.depth.belowSurface": 15.8, "environment.wind.directionTrue": 0.47123889814606673, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.611666666666668, "propulsion.engine_2.revolutions": 25.87, "environment.depth.belowTransducer": 15.8, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.47123889814606673, "navigation.gnss.horizontalDilution": 0.8, "navigation.courseOverGroundMagnetic": 0.054105206824178034, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670967595}
},
{
"time" : "2022-12-13 21:41:06.155",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.458240233333335,
"longitude" : 2.2926219333333333,
"speedoverground" : 4.7,
"courseovergroundtrue" : 3.8,
"windspeedapparent" : 12.4,
"anglespeedapparent" : 9.2,
"status" : "motoring",
"metrics" : {"navigation.headingTrue": 0.0663225115909279, "environment.wind.speedTrue": 1.6, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.0663225115909279, "environment.depth.belowKeel": 14.4, "navigation.speedThroughWater": 2.417889501420896, "environment.water.temperature": 279.95, "environment.depth.belowSurface": 14.4, "environment.wind.directionTrue": 0.47123889814606673, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.611666666666668, "propulsion.engine_2.revolutions": 26.916666666666668, "environment.depth.belowTransducer": 14.4, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.47123889814606673, "navigation.gnss.horizontalDilution": 0.8, "navigation.courseOverGroundMagnetic": 0.0663225115909279, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670967655}
},
{
"time" : "2022-12-13 21:42:06.187",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.45955073333333,
"longitude" : 2.2927578,
"speedoverground" : 4.8,
"courseovergroundtrue" : 4.7,
"windspeedapparent" : 9.8,
"anglespeedapparent" : 12.4,
"status" : "motoring",
"metrics" : {"navigation.headingTrue": 0.08203047486246347, "environment.wind.speedTrue": 1.6, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.08203047486246347, "environment.depth.belowKeel": 13, "navigation.speedThroughWater": 2.4693339588979364, "environment.water.temperature": 279.84999999999997, "environment.depth.belowSurface": 13, "environment.wind.directionTrue": 0.6318091893662081, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 11.04, "propulsion.engine_2.revolutions": 25.87, "environment.depth.belowTransducer": 13, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.6318091893662081, "navigation.gnss.horizontalDilution": 0.6, "navigation.courseOverGroundMagnetic": 0.08203047486246347, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670967715}
},
{
"time" : "2022-12-13 21:43:06.208",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.46092023333333,
"longitude" : 2.2929017833333334,
"speedoverground" : 5.0,
"courseovergroundtrue" : 4.2,
"windspeedapparent" : 8.2,
"anglespeedapparent" : 11.7,
"status" : "motoring",
"metrics" : {"navigation.headingTrue": 0.07330382860049928, "environment.wind.speedTrue": 1.8, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.07330382860049928, "environment.depth.belowKeel": 13, "navigation.speedThroughWater": 2.572222873852017, "environment.water.temperature": 279.84999999999997, "environment.depth.belowSurface": 13, "environment.wind.directionTrue": 0.5742133240372442, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 11.04, "propulsion.engine_2.revolutions": 28.003333333333334, "environment.depth.belowTransducer": 13, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.5742133240372442, "navigation.gnss.horizontalDilution": 0.5, "navigation.courseOverGroundMagnetic": 0.07330382860049928, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670967775}
},
{
"time" : "2022-12-13 21:44:06.237",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.46235585,
"longitude" : 2.2930669666666668,
"speedoverground" : 5.3,
"courseovergroundtrue" : 5.1,
"windspeedapparent" : 10.6,
"anglespeedapparent" : 13.7,
"status" : "motoring",
"metrics" : {"navigation.headingTrue": 0.08901179187203483, "environment.wind.speedTrue": 1.8, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.08901179187203483, "environment.depth.belowKeel": 11.8, "navigation.speedThroughWater": 2.726556246283138, "environment.water.temperature": 279.65, "environment.depth.belowSurface": 11.8, "environment.wind.directionTrue": 0.698131700957136, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 11.04, "propulsion.engine_2.revolutions": 25.87, "environment.depth.belowTransducer": 11.8, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.698131700957136, "navigation.gnss.horizontalDilution": 0.6, "navigation.courseOverGroundMagnetic": 0.08901179187203483, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670967835}
},
{
"time" : "2022-12-13 21:45:06.284",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.46385398333334,
"longitude" : 2.2932485833333334,
"speedoverground" : 5.4,
"courseovergroundtrue" : 5.7,
"windspeedapparent" : 7.9,
"anglespeedapparent" : 7.3,
"status" : "motoring",
"metrics" : {"navigation.headingTrue": 0.09948376738639188, "environment.wind.speedTrue": 1, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.09948376738639188, "environment.depth.belowKeel": 9.7, "navigation.speedThroughWater": 2.7780007037601786, "environment.water.temperature": 280.04999999999995, "environment.depth.belowSurface": 9.7, "environment.wind.directionTrue": 0.5742133240372442, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 11.04, "propulsion.engine_2.revolutions": 23.9, "environment.depth.belowTransducer": 9.7, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.5742133240372442, "navigation.gnss.horizontalDilution": 0.5, "navigation.courseOverGroundMagnetic": 0.09948376738639188, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670967895}
},
{
"time" : "2022-12-13 21:46:06.327",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.46533328333334,
"longitude" : 2.2934791,
"speedoverground" : 5.5,
"courseovergroundtrue" : 6.9,
"windspeedapparent" : 9.3,
"anglespeedapparent" : 11.3,
"status" : "motoring",
"metrics" : {"navigation.headingTrue": 0.12042771841510595, "environment.wind.speedTrue": 1.8, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.12042771841510595, "environment.depth.belowKeel": 9.7, "navigation.speedThroughWater": 2.829445161237219, "environment.water.temperature": 280.25, "environment.depth.belowSurface": 9.7, "environment.wind.directionTrue": 0.6318091893662081, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 12.433333333333334, "propulsion.engine_2.revolutions": 22.971666666666668, "environment.depth.belowTransducer": 9.7, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.6318091893662081, "navigation.gnss.horizontalDilution": 0.6, "navigation.courseOverGroundMagnetic": 0.12042771841510595, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670967955}
},
{
"time" : "2022-12-13 21:47:06.327",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.46533328333334,
"longitude" : 2.2934791,
"speedoverground" : 5.5,
"courseovergroundtrue" : 6.9,
"windspeedapparent" : 9.3,
"anglespeedapparent" : 11.3,
"status" : "motoring",
"metrics" : {"navigation.headingTrue": 0.12042771841510595, "environment.wind.speedTrue": 1.8, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.12042771841510595, "environment.depth.belowKeel": 9.7, "navigation.speedThroughWater": 2.829445161237219, "environment.water.temperature": 280.25, "environment.depth.belowSurface": 9.7, "environment.wind.directionTrue": 0.6318091893662081, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 12.433333333333334, "propulsion.engine_2.revolutions": 22.971666666666668, "environment.depth.belowTransducer": 9.7, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.6318091893662081, "navigation.gnss.horizontalDilution": 0.6, "navigation.courseOverGroundMagnetic": 0.12042771841510595, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670967955}
},
{
"time" : "2022-12-13 21:48:06.327",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.46533328333334,
"longitude" : 2.2934791,
"speedoverground" : 5.5,
"courseovergroundtrue" : 6.9,
"windspeedapparent" : 9.3,
"anglespeedapparent" : 11.3,
"status" : "motoring",
"metrics" : {"navigation.headingTrue": 0.12042771841510595, "environment.wind.speedTrue": 1.8, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.12042771841510595, "environment.depth.belowKeel": 9.7, "navigation.speedThroughWater": 2.829445161237219, "environment.water.temperature": 280.25, "environment.depth.belowSurface": 9.7, "environment.wind.directionTrue": 0.6318091893662081, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 12.433333333333334, "propulsion.engine_2.revolutions": 22.971666666666668, "environment.depth.belowTransducer": 9.7, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.6318091893662081, "navigation.gnss.horizontalDilution": 0.6, "navigation.courseOverGroundMagnetic": 0.12042771841510595, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670967955}
},
{
"time" : "2022-12-13 21:49:06.327",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.46533328333334,
"longitude" : 2.2934791,
"speedoverground" : 5.5,
"courseovergroundtrue" : 6.9,
"windspeedapparent" : 9.3,
"anglespeedapparent" : 11.3,
"status" : "motoring",
"metrics" : {"navigation.headingTrue": 0.12042771841510595, "environment.wind.speedTrue": 1.8, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.12042771841510595, "environment.depth.belowKeel": 9.7, "navigation.speedThroughWater": 2.829445161237219, "environment.water.temperature": 280.25, "environment.depth.belowSurface": 9.7, "environment.wind.directionTrue": 0.6318091893662081, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 12.433333333333334, "propulsion.engine_2.revolutions": 22.971666666666668, "environment.depth.belowTransducer": 9.7, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.6318091893662081, "navigation.gnss.horizontalDilution": 0.6, "navigation.courseOverGroundMagnetic": 0.12042771841510595, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670967955}
},
{
"time" : "2022-12-13 21:50:06.327",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.46533328333334,
"longitude" : 2.2934791,
"speedoverground" : 5.5,
"courseovergroundtrue" : 6.9,
"windspeedapparent" : 9.3,
"anglespeedapparent" : 11.3,
"status" : "motoring",
"metrics" : {"navigation.headingTrue": 0.12042771841510595, "environment.wind.speedTrue": 1.8, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.12042771841510595, "environment.depth.belowKeel": 9.7, "navigation.speedThroughWater": 2.829445161237219, "environment.water.temperature": 280.25, "environment.depth.belowSurface": 9.7, "environment.wind.directionTrue": 0.6318091893662081, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 12.433333333333334, "propulsion.engine_2.revolutions": 22.971666666666668, "environment.depth.belowTransducer": 9.7, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.6318091893662081, "navigation.gnss.horizontalDilution": 0.6, "navigation.courseOverGroundMagnetic": 0.12042771841510595, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670967955}
},
{
"time" : "2022-12-13 21:51:06.327",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.46533328333334,
"longitude" : 2.2934791,
"speedoverground" : 5.5,
"courseovergroundtrue" : 6.9,
"windspeedapparent" : 9.3,
"anglespeedapparent" : 11.3,
"status" : "moored",
"metrics" : {"navigation.headingTrue": 0.12042771841510595, "environment.wind.speedTrue": 1.8, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.12042771841510595, "environment.depth.belowKeel": 9.7, "navigation.speedThroughWater": 2.829445161237219, "environment.water.temperature": 280.25, "environment.depth.belowSurface": 9.7, "environment.wind.directionTrue": 0.6318091893662081, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 12.433333333333334, "propulsion.engine_2.revolutions": 22.971666666666668, "environment.depth.belowTransducer": 9.7, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.6318091893662081, "navigation.gnss.horizontalDilution": 0.6, "navigation.courseOverGroundMagnetic": 0.12042771841510595, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670967955}
}
]}

12
tests/package.json Normal file
View File

@@ -0,0 +1,12 @@
{
"scripts": {
"tests": "mocha index.js"
},
"dependencies": {
"mocha": "^10.2.0",
"mochawesome": "^7.1.3",
"moment": "^2.29.4",
"should": "^13.2.3",
"supertest": "^6.3.3"
}
}

20
tests/sql/anonymous.sql Normal file
View File

@@ -0,0 +1,20 @@
---------------------------------------------------------------------------
-- Listing
--
-- List current database
select current_database();
-- connect to the DB
\c signalk
-- output display format
\x on
\echo 'Validate anonymous access'
SELECT api.ispublic_fn('kapla', 'public_test');
SELECT api.ispublic_fn('kapla', 'public_logs_list');
SELECT api.ispublic_fn('kapla', 'public_logs', 1);
SELECT api.ispublic_fn('kapla', 'public_logs', 3);
SELECT api.ispublic_fn('kapla', 'public_monitoring');
SELECT api.ispublic_fn('kapla', 'public_timelapse');

View File

@@ -0,0 +1,23 @@
current_database
------------------
signalk
(1 row)
You are now connected to database "signalk" as user "username".
Expanded display is on.
Validate anonymous access
-[ RECORD 1 ]--
ispublic_fn | f
-[ RECORD 1 ]--
ispublic_fn | f
-[ RECORD 1 ]--
ispublic_fn | t
-[ RECORD 1 ]--
ispublic_fn | t
-[ RECORD 1 ]--
ispublic_fn | f

85
tests/sql/badges.sql Normal file
View File

@@ -0,0 +1,85 @@
---------------------------------------------------------------------------
-- Listing
--
-- List current database
select current_database();
-- connect to the DB
\c signalk
-- output display format
\x on
SELECT v.vessel_id as "vessel_id" FROM auth.vessels v WHERE v.owner_email = 'demo+kapla@openplotter.cloud' \gset
--\echo :"vessel_id"
SELECT set_config('vessel.id', :'vessel_id', false) IS NOT NULL as vessel_id;
\echo 'Insert new api.logbook for badges'
INSERT INTO api.logbook
(id, active, "name", "_from", "_from_lat", "_from_lng", "_to", "_to_lat", "_to_lng", track_geom, track_geog, track_geojson, "_from_time", "_to_time", distance, duration, avg_speed, max_speed, max_wind_speed, notes, vessel_id)
OVERRIDING SYSTEM VALUE VALUES
(nextval('api.logbook_id_seq'), false, 'Tropics Zone', NULL, NULL, NULL, NULL, NULL, NULL, 'SRID=4326;LINESTRING (-63.151124640791096 14.01074681627324, -77.0912026418618 12.870995731013664)'::public.geometry, NULL, NULL, NOW(), NOW(), 123, NULL, NULL, NULL, NULL, NULL, current_setting('vessel.id', false)),
(nextval('api.logbook_id_seq'), false, 'Alaska Zone', NULL, NULL, NULL, NULL, NULL, NULL, 'SRID=4326;LINESTRING (-143.5773697471158 59.4404631255976, -152.35402122385003 56.58243132943173)'::public.geometry, NULL, NULL, NOW(), NOW(), 1234, NULL, NULL, NULL, NULL, NULL, current_setting('vessel.id', false));
\echo 'Set config'
SELECT set_config('user.email', 'demo+kapla@openplotter.cloud', false);
--SELECT set_config('vessel.client_id', 'vessels.urn:mrn:imo:mmsi:123456789', false);
\echo 'Process badge'
SELECT badges_logbook_fn(5);
SELECT badges_logbook_fn(6);
SELECT badges_geom_fn(5);
SELECT badges_geom_fn(6);
\echo 'Check badges for user'
SELECT jsonb_object_keys ( a.preferences->'badges' ) FROM auth.accounts a;
\echo 'Check details from vessel_id kapla'
--SELECT get_user_settings_from_vesselid_fn('vessels.urn:mrn:imo:mmsi:123456789'::TEXT);
SELECT
json_build_object(
'boat', v.name,
'recipient', a.first,
'email', v.owner_email,
--'settings', a.preferences,
'pushover_key', a.preferences->'pushover_key'
--'badges', a.preferences->'badges'
) as user_settings
FROM auth.accounts a, auth.vessels v, api.metadata m
WHERE m.vessel_id = v.vessel_id
AND m.vessel_id = current_setting('vessel.id', false)
AND lower(a.email) = current_setting('user.email', false);
\echo 'Insert new api.moorages for badges'
INSERT INTO api.moorages
(id,"name",country,stay_code,stay_duration,reference_count,latitude,longitude,geog,home_flag,notes,vessel_id)
OVERRIDING SYSTEM VALUE VALUES
(8,'Badge Mooring Pro',NULL,3,'11 days 00:39:56.418',1,NULL,NULL,NULL,false,'Badge Mooring Pro',current_setting('vessel.id', false)),
(9,'Badge Anchormaster',NULL,2,'26 days 00:49:56.418',1,NULL,NULL,NULL,false,'Badge Anchormaster',current_setting('vessel.id', false));
\echo 'Set config'
SELECT set_config('user.email', 'demo+aava@openplotter.cloud', false);
--SELECT set_config('vessel.client_id', 'vessels.urn:mrn:imo:mmsi:787654321', false);
SELECT v.vessel_id as "vessel_id" FROM auth.vessels v WHERE v.owner_email = 'demo+aava@openplotter.cloud' \gset
--\echo :"vessel_id"
SELECT set_config('vessel.id', :'vessel_id', false) IS NOT NULL as vessel_id;
\echo 'Process badge'
SELECT badges_moorages_fn();
\echo 'Check details from vessel_id aava'
--SELECT get_user_settings_from_vesselid_fn('vessels.urn:mrn:imo:mmsi:787654321'::TEXT);
SELECT
json_build_object(
'boat', v.name,
'recipient', a.first,
'email', v.owner_email,
--'settings', a.preferences,
'pushover_key', a.preferences->'pushover_key'
--'badges', a.preferences->'badges'
) as user_settings
FROM auth.accounts a, auth.vessels v, api.metadata m
WHERE m.vessel_id = v.vessel_id
AND m.vessel_id = current_setting('vessel.id', false)
AND lower(a.email) = current_setting('user.email', false);

View File

@@ -0,0 +1,82 @@
current_database
------------------
signalk
(1 row)
You are now connected to database "signalk" as user "username".
Expanded display is on.
-[ RECORD 1 ]
vessel_id | t
Insert new api.logbook for badges
INSERT 0 2
Set config
-[ RECORD 1 ]----------------------------
set_config | demo+kapla@openplotter.cloud
Process badge
-[ RECORD 1 ]-----+-
badges_logbook_fn |
-[ RECORD 1 ]-----+-
badges_logbook_fn |
-[ RECORD 1 ]--+-
badges_geom_fn |
-[ RECORD 1 ]--+-
badges_geom_fn |
Check badges for user
-[ RECORD 1 ]-----+------------------
jsonb_object_keys | Helmsman
-[ RECORD 2 ]-----+------------------
jsonb_object_keys | Wake Maker
-[ RECORD 3 ]-----+------------------
jsonb_object_keys | Balearic Sea
-[ RECORD 4 ]-----+------------------
jsonb_object_keys | Stormtrooper
-[ RECORD 5 ]-----+------------------
jsonb_object_keys | Gulf of Finland
-[ RECORD 6 ]-----+------------------
jsonb_object_keys | Helmsman
-[ RECORD 7 ]-----+------------------
jsonb_object_keys | Wake Maker
-[ RECORD 8 ]-----+------------------
jsonb_object_keys | Club Alaska
-[ RECORD 9 ]-----+------------------
jsonb_object_keys | Stormtrooper
-[ RECORD 10 ]----+------------------
jsonb_object_keys | Captain Award
-[ RECORD 11 ]----+------------------
jsonb_object_keys | Caribbean Sea
-[ RECORD 12 ]----+------------------
jsonb_object_keys | Gulf of Alaska
-[ RECORD 13 ]----+------------------
jsonb_object_keys | Gulf of Finland
-[ RECORD 14 ]----+------------------
jsonb_object_keys | Navigator Award
-[ RECORD 15 ]----+------------------
jsonb_object_keys | Tropical Traveler
Check details from vessel_id kapla
-[ RECORD 1 ]-+-----------------------------------------------------------------------------------------------------------------
user_settings | {"boat" : "kapla", "recipient" : "First_kapla", "email" : "demo+kapla@openplotter.cloud", "pushover_key" : null}
Insert new api.moorages for badges
INSERT 0 2
Set config
-[ RECORD 1 ]---------------------------
set_config | demo+aava@openplotter.cloud
-[ RECORD 1 ]
vessel_id | t
Process badge
-[ RECORD 1 ]------+-
badges_moorages_fn |
Check details from vessel_id aava
-[ RECORD 1 ]-+--------------------------------------------------------------------------------------------------------------
user_settings | {"boat" : "aava", "recipient" : "first_aava", "email" : "demo+aava@openplotter.cloud", "pushover_key" : null}

View File

@@ -0,0 +1,71 @@
---------------------------------------------------------------------------
-- Listing
--
-- List current database
select current_database();
-- connect to the DB
\c signalk
-- output display format
\x on
-- set user_id
SELECT a.user_id as "user_id" FROM auth.accounts a WHERE a.email = 'demo+kapla@openplotter.cloud' \gset
--\echo :"user_id"
SELECT set_config('user.id', :'user_id', false) IS NOT NULL as user_id;
-- set vessel_id
SELECT v.vessel_id as "vessel_id" FROM auth.vessels v WHERE v.owner_email = 'demo+kapla@openplotter.cloud' \gset
--\echo :"vessel_id"
SELECT set_config('vessel.id', :'vessel_id', false) IS NOT NULL as vessel_id;
-- Test logbook for user
\echo 'logbook'
SELECT count(*) FROM api.logbook WHERE vessel_id = current_setting('vessel.id', false);
\echo 'logbook'
SELECT name,_from_time IS NOT NULL AS _from_time,_to_time IS NOT NULL AS _to_time, track_geojson IS NOT NULL AS track_geojson, track_geom, distance,duration,avg_speed,max_speed,max_wind_speed,notes,extra FROM api.logbook WHERE vessel_id = current_setting('vessel.id', false);
-- Test stays for user
\echo 'stays'
SELECT count(*) FROM api.stays WHERE vessel_id = current_setting('vessel.id', false);
\echo 'stays'
SELECT active,name,geog,stay_code FROM api.stays WHERE vessel_id = current_setting('vessel.id', false);
-- Test event logs view for user
\echo 'eventlogs_view'
SELECT count(*) from api.eventlogs_view;
-- Test event logs view for user
\echo 'stats_logs_fn'
SELECT api.stats_logs_fn(null, null) INTO stats_jsonb;
SELECT stats_logs_fn->'name' AS name,
stats_logs_fn->'count' AS count,
stats_logs_fn->'max_speed' As max_speed,
stats_logs_fn->'max_distance' AS max_distance,
stats_logs_fn->'max_duration' AS max_duration,
stats_logs_fn->'max_speed_id',
stats_logs_fn->'sum_distance',
stats_logs_fn->'sum_duration',
stats_logs_fn->'max_wind_speed',
stats_logs_fn->'max_distance_id',
stats_logs_fn->'max_duration_id',
stats_logs_fn->'max_wind_speed_id',
stats_logs_fn->'first_date' IS NOT NULL AS first_date,
stats_logs_fn->'last_date' IS NOT NULL AS last_date
FROM stats_jsonb;
DROP TABLE stats_jsonb;
SELECT api.stats_logs_fn('2022-01-01'::text,'2022-06-12'::text);
-- Update logbook observations
\echo 'update_logbook_observations_fn'
SELECT extra FROM api.logbook l WHERE id = 1 AND vessel_id = current_setting('vessel.id', false);
SELECT api.update_logbook_observations_fn(1, '{"observations":{"cloudCoverage":1}}'::TEXT);
SELECT extra FROM api.logbook l WHERE id = 1 AND vessel_id = current_setting('vessel.id', false);
-- Check export
--\echo 'check logbook export fn'
--SELECT api.export_logbook_geojson_fn(1);
--SELECT api.export_logbook_gpx_fn(1);
--SELECT api.export_logbook_kml_fn(1);

View File

@@ -0,0 +1,102 @@
current_database
------------------
signalk
(1 row)
You are now connected to database "signalk" as user "username".
Expanded display is on.
-[ RECORD 1 ]
user_id | t
-[ RECORD 1 ]
vessel_id | t
logbook
-[ RECORD 1 ]
count | 2
logbook
-[ RECORD 1 ]--+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
name | Pojoviken to Norra hamnen
_from_time | t
_to_time | t
track_geojson | t
track_geom | 0102000020E61000001C000000B0DEBBE0E68737404DA938FBF0094E40B0DEBBE0E68737404DA938FBF0094E4020D26F5F0786374030BB270F0B094E400C6E7ED60F843740AA60545227084E40D60FC48C03823740593CE27D42074E407B39D9F322803740984C158C4A064E4091ED7C3F357E3740898BB63D54054E40A8A1208B477C37404BA3DC9059044E404C5CB4EDA17A3740C4F856115B034E40A9A44E4013793740D8F0F44A59024E40E4839ECDAA773740211FF46C56014E405408D147067637408229F03B73004E40787AA52C43743740F90FE9B7AFFF4D40F8098D4D18723740C217265305FF4D4084E82303537037409A2D464AA0FE4D4022474DCE636F37402912396A72FE4D408351499D806E374088CFB02B40FE4D4076711B0DE06D3740B356C7040FFE4D404EAC66B0BC6E374058A835CD3BFE4D40D7A3703D0A6F3740D3E10EC15EFE4D4087602F277B6E3740A779C7293AFE4D4087602F277B6E3740A779C7293AFE4D402063EE5A426E3740B5A679C729FE4D40381DEE10EC6D37409ECA7C1A0AFE4D40E2C46A06CB6B37400A43F7BF36FD4D4075931804566E3740320BDAD125FD4D409A2D464AA06E37404A5658830AFD4D40029A081B9E6E37404A5658830AFD4D40
distance | 7.6447
duration | PT27M
avg_speed | 3.6357142857142852
max_speed | 6.1
max_wind_speed | 22.1
notes | new log note
extra | {"metrics": {"propulsion.main.runTime": 10}, "observations": {"seaState": -1, "visibility": -1, "cloudCoverage": -1}}
-[ RECORD 2 ]--+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
name | Norra hamnen to Ekenäs
_from_time | t
_to_time | t
track_geojson | t
track_geom | 0102000020E610000015000000029A081B9E6E37404A5658830AFD4D40029A081B9E6E37404A5658830AFD4D404806A6C0EF6C3740DA1B7C6132FD4D40FE65F7E461693740226C787AA5FC4D407DD3E10EC1663740B29DEFA7C6FB4D40898BB63D5465374068479724BCFA4D409A5271F6E1633740B6847CD0B3F94D40431CEBE236623740E9263108ACF84D402C6519E2585F37407E678EBFC7F74D4096218E75715B374027C5B45C23F74D402AA913D044583740968DE1C46AF64D405AF5B9DA8A5537407BEF829B9FF54D407449C2ABD253374086C954C1A8F44D407D1A0AB278543740F2B0506B9AF34D409D11A5BDC15737406688635DDCF24D4061C3D32B655937402CAF6F3ADCF14D408988888888583740B3319C58CDF04D4021FAC8C0145837408C94405DB7EF4D40B8F9593F105B37403DC0804BEDEE4D40DE4C5FE2A25D3740AE47E17A14EE4D40DE4C5FE2A25D3740AE47E17A14EE4D40
distance | 8.8968
duration | PT20M
avg_speed | 5.4523809523809526
max_speed | 6.5
max_wind_speed | 37.2
notes |
extra | {"metrics": {"propulsion.main.runTime": 11}, "observations": {"seaState": -1, "visibility": -1, "cloudCoverage": -1}}
stays
-[ RECORD 1 ]
count | 3
stays
-[ RECORD 1 ]-------------------------------------------------
active | t
name |
geog |
stay_code | 2
-[ RECORD 2 ]-------------------------------------------------
active | f
name | 0 days stay at Pojoviken in November 2023
geog | 0101000020E6100000B0DEBBE0E68737404DA938FBF0094E40
stay_code | 2
-[ RECORD 3 ]-------------------------------------------------
active | f
name | 0 days stay at Norra hamnen in November 2023
geog | 0101000020E6100000029A081B9E6E37404A5658830AFD4D40
stay_code | 4
eventlogs_view
-[ RECORD 1 ]
count | 11
stats_logs_fn
SELECT 1
-[ RECORD 1 ]+----------
name | "kapla"
count | 4
max_speed | 7.1
max_distance | 8.8968
max_duration | "PT1H11M"
?column? | 3
?column? | 30.1154
?column? | "PT2H43M"
?column? | 44.2
?column? | 2
?column? | 4
?column? | 4
first_date | t
last_date | t
DROP TABLE
-[ RECORD 1 ]-+-
stats_logs_fn |
update_logbook_observations_fn
-[ RECORD 1 ]----------------------------------------------------------------------------------------------------------------
extra | {"metrics": {"propulsion.main.runTime": 10}, "observations": {"seaState": -1, "visibility": -1, "cloudCoverage": -1}}
-[ RECORD 1 ]------------------+--
update_logbook_observations_fn | t
-[ RECORD 1 ]---------------------------------------------------------------------------------------------------------------
extra | {"metrics": {"propulsion.main.runTime": 10}, "observations": {"seaState": -1, "visibility": -1, "cloudCoverage": 1}}

View File

@@ -0,0 +1,20 @@
---------------------------------------------------------------------------
-- Listing
--
-- List current database
select current_database();
-- connect to the DB
\c signalk
-- output display format
\x on
-- Check the number of process pending
-- Should be 22
SELECT count(*) as jobs from public.process_queue pq where pq.processed is null;
--set role scheduler
SELECT public.run_cron_jobs();
-- Check any pending job
SELECT count(*) as any_pending_jobs from public.process_queue pq where pq.processed is null;

View File

@@ -0,0 +1,16 @@
current_database
------------------
signalk
(1 row)
You are now connected to database "signalk" as user "username".
Expanded display is on.
-[ RECORD 1 ]
jobs | 24
-[ RECORD 1 ]-+-
run_cron_jobs |
-[ RECORD 1 ]----+--
any_pending_jobs | 0

79
tests/sql/grafana.sql Normal file
View File

@@ -0,0 +1,79 @@
---------------------------------------------------------------------------
-- Listing
--
-- List current database
select current_database();
-- connect to the DB
\c signalk
-- output display format
\x on
--
-- grafana_auth
SET ROLE grafana_auth;
\echo 'ROLE grafana_auth current_setting'
SELECT current_user, current_setting('user.email', true), current_setting('vessel.client_id', true), current_setting('vessel.id', true);
--SELECT a.pass,v.name,m.client_id FROM auth.accounts a JOIN auth.vessels v ON a.email = 'demo+kapla@openplotter.cloud' AND a.role = 'user_role' AND cast(a.preferences->>'email_valid' as Boolean) = True AND v.owner_email = a.email JOIN api.metadata m ON m.vessel_id = v.vessel_id;
--SELECT a.pass,v.name,m.client_id FROM auth.accounts a JOIN auth.vessels v ON a.email = 'demo+kapla@openplotter.cloud' AND a.role = 'user_role' AND v.owner_email = a.email JOIN api.metadata m ON m.vessel_id = v.vessel_id;
\echo 'link vessel and user based on current_setting'
SELECT v.name,m.client_id FROM auth.accounts a JOIN auth.vessels v ON a.role = 'user_role' AND v.owner_email = a.email JOIN api.metadata m ON m.vessel_id = v.vessel_id;
\echo 'auth.accounts details'
SELECT a.user_id IS NOT NULL AS user_id, a.email, a.first, a.last, a.pass IS NOT NULL AS pass, a.role, a.preferences->'telegram'->'chat' AS telegram, a.preferences->'pushover_user_key' AS pushover_user_key FROM auth.accounts AS a;
\echo 'auth.vessels details'
--SELECT 'SELECT ' || STRING_AGG('v.' || column_name, ', ') || ' FROM auth.vessels AS v' FROM information_schema.columns WHERE table_name = 'vessels' AND table_schema = 'auth' AND column_name NOT IN ('created_at', 'updated_at');
SELECT v.vessel_id IS NOT NULL AS vessel_id, v.owner_email, v.mmsi, v.name, v.role FROM auth.vessels AS v;
\echo 'api.metadata details'
--
SELECT m.id, m.name, m.mmsi, m.client_id, m.length, m.beam, m.height, m.ship_type, m.plugin_version, m.signalk_version, m.time IS NOT NULL AS time, m.active FROM api.metadata AS m;
--
-- grafana
SET ROLE grafana;
\echo 'ROLE grafana current_setting'
\echo 'Set current_setting value'
SET "user.email" = 'demo+kapla@openplotter.cloud';
--SET vessel.client_id = 'vessels.urn:mrn:imo:mmsi:123456789';
--select v.vessel_id FROM auth.vessels v WHERE v.owner_email = 'demo+kapla@openplotter.cloud';
SELECT v.vessel_id as "vessel_id" FROM auth.vessels v WHERE v.owner_email = 'demo+kapla@openplotter.cloud' \gset
--\echo :"vessel_id"
SELECT set_config('vessel.id', :'vessel_id', false) IS NOT NULL as vessel_id;
--SELECT current_user, current_setting('user.email', true), current_setting('vessel.client_id', true), current_setting('vessel.id', true);
SELECT current_user, current_setting('user.email', true), current_setting('vessel.client_id', true);
SELECT v.name AS __text, m.client_id AS __value FROM auth.vessels v JOIN api.metadata m ON v.owner_email = 'demo+kapla@openplotter.cloud' and m.vessel_id = v.vessel_id;
\echo 'auth.vessels details'
--SELECT * FROM auth.vessels v;
SELECT v.vessel_id IS NOT NULL AS vessel_id, v.owner_email, v.mmsi, v.name, v.role FROM auth.vessels AS v;
--SELECT * FROM api.metadata m;
\echo 'api.metadata details'
SELECT m.id, m.name, m.mmsi, m.client_id, m.length, m.beam, m.height, m.ship_type, m.plugin_version, m.signalk_version, m.time IS NOT NULL AS time, m.active FROM api.metadata AS m;
\echo 'api.logs_view'
--SELECT * FROM api.logbook l;
--SELECT * FROM api.logs_view l;
SELECT l.id, l.name, l.from, l.to, l.distance, l.duration, l._from_moorage_id, l._to_moorage_id FROM api.logs_view AS l;
--SELECT * FROM api.log_view l;
\echo 'api.stays'
--SELECT * FROM api.stays s;
SELECT m.id, m.vessel_id IS NOT NULL AS vessel_id, m.moorage_id, m.active, m.name, m.latitude, m.longitude, m.geog, m.arrived IS NOT NULL AS arrived, m.departed IS NOT NULL AS departed, m.duration, m.stay_code, m.notes FROM api.stays AS m;
\echo 'stays_view'
--SELECT * FROM api.stays_view s;
SELECT m.id, m.name IS NOT NULL AS name, m.moorage, m.moorage_id, m.duration, m.stayed_at, m.stayed_at_id, m.arrived IS NOT NULL AS arrived, m.departed IS NOT NULL AS departed, m.notes FROM api.stays_view AS m;
\echo 'api.moorages'
--SELECT * FROM api.moorages m;
SELECT m.id, m.vessel_id IS NOT NULL AS vessel_id, m.name, m.country, m.stay_code, m.stay_duration, m.reference_count, m.latitude, m.longitude, m.geog, m.home_flag, m.notes FROM api.moorages AS m;
\echo 'api.moorages_view'
SELECT * FROM api.moorages_view s;

View File

@@ -0,0 +1,271 @@
current_database
------------------
signalk
(1 row)
You are now connected to database "signalk" as user "username".
Expanded display is on.
SET
ROLE grafana_auth current_setting
-[ RECORD 1 ]---+-------------
current_user | grafana_auth
current_setting |
current_setting |
current_setting |
link vessel and user based on current_setting
-[ RECORD 1 ]----------------------------------------------------------------
name | aava
client_id | vessels.urn:mrn:imo:mmsi:787654321
-[ RECORD 2 ]----------------------------------------------------------------
name | kapla
client_id | vessels.urn:mrn:signalk:uuid:5b4f7543-7153-4840-b139-761310b242fd
auth.accounts details
-[ RECORD 1 ]-----+-----------------------------
user_id | t
email | demo+aava@openplotter.cloud
first | first_aava
last | last_aava
pass | t
role | user_role
telegram |
pushover_user_key |
-[ RECORD 2 ]-----+-----------------------------
user_id | t
email | demo+kapla@openplotter.cloud
first | First_kapla
last | Last_kapla
pass | t
role | user_role
telegram |
pushover_user_key |
auth.vessels details
-[ RECORD 1 ]-----------------------------
vessel_id | t
owner_email | demo+kapla@openplotter.cloud
mmsi |
name | kapla
role | vessel_role
-[ RECORD 2 ]-----------------------------
vessel_id | t
owner_email | demo+aava@openplotter.cloud
mmsi | 787654321
name | aava
role | vessel_role
api.metadata details
-[ RECORD 1 ]---+------------------------------------------------------------------
id | 1
name | kapla
mmsi | 123456789
client_id | vessels.urn:mrn:signalk:uuid:5b4f7543-7153-4840-b139-761310b242fd
length | 12
beam | 10
height | 24
ship_type | 36
plugin_version | 0.0.1
signalk_version | signalk_version
time | t
active | t
-[ RECORD 2 ]---+------------------------------------------------------------------
id | 2
name | aava
mmsi | 787654321
client_id | vessels.urn:mrn:imo:mmsi:787654321
length | 12
beam | 10
height | 24
ship_type | 37
plugin_version | 1.0.2
signalk_version | 1.20.0
time | t
active | t
SET
ROLE grafana current_setting
Set current_setting value
SET
-[ RECORD 1 ]
vessel_id | t
-[ RECORD 1 ]---+-----------------------------
current_user | grafana
current_setting | demo+kapla@openplotter.cloud
current_setting |
-[ RECORD 1 ]--------------------------------------------------------------
__text | kapla
__value | vessels.urn:mrn:signalk:uuid:5b4f7543-7153-4840-b139-761310b242fd
auth.vessels details
-[ RECORD 1 ]-----------------------------
vessel_id | t
owner_email | demo+kapla@openplotter.cloud
mmsi |
name | kapla
role | vessel_role
api.metadata details
-[ RECORD 1 ]---+------------------------------------------------------------------
id | 1
name | kapla
mmsi | 123456789
client_id | vessels.urn:mrn:signalk:uuid:5b4f7543-7153-4840-b139-761310b242fd
length | 12
beam | 10
height | 24
ship_type | 36
plugin_version | 0.0.1
signalk_version | signalk_version
time | t
active | t
api.logs_view
-[ RECORD 1 ]----+-----------------------
id | 2
name | Norra hamnen to Ekenäs
from | Norra hamnen
to | Ekenäs
distance | 8.8968
duration | PT20M
_from_moorage_id | 2
_to_moorage_id | 3
-[ RECORD 2 ]----+-----------------------
id | 1
name | patch log name 3
from | patch moorage name 3
to | Norra hamnen
distance | 7.6447
duration | PT27M
_from_moorage_id | 1
_to_moorage_id | 2
api.stays
-[ RECORD 1 ]--------------------------------------------------
id | 3
vessel_id | t
moorage_id |
active | t
name |
latitude | 59.86
longitude | 23.365766666666666
geog |
arrived | t
departed | f
duration |
stay_code | 2
notes |
-[ RECORD 2 ]--------------------------------------------------
id | 1
vessel_id | t
moorage_id | 1
active | f
name | patch stay name 3
latitude | 60.077666666666666
longitude | 23.530866666666668
geog | 0101000020E6100000B0DEBBE0E68737404DA938FBF0094E40
arrived | t
departed | t
duration | PT1M
stay_code | 2
notes | new stay note 3
-[ RECORD 3 ]--------------------------------------------------
id | 2
vessel_id | t
moorage_id | 2
active | f
name | 0 days stay at Norra hamnen in November 2023
latitude | 59.97688333333333
longitude | 23.4321
geog | 0101000020E6100000029A081B9E6E37404A5658830AFD4D40
arrived | t
departed | t
duration | PT2M
stay_code | 4
notes |
stays_view
-[ RECORD 1 ]+---------------------
id | 2
name | t
moorage | Norra hamnen
moorage_id | 2
duration | PT2M
stayed_at | Dock
stayed_at_id | 4
arrived | t
departed | t
notes |
-[ RECORD 2 ]+---------------------
id | 1
name | t
moorage | patch moorage name 3
moorage_id | 1
duration | PT1M
stayed_at | Anchor
stayed_at_id | 2
arrived | t
departed | t
notes | new stay note 3
api.moorages
-[ RECORD 1 ]---+---------------------------------------------------
id | 1
vessel_id | t
name | patch moorage name 3
country |
stay_code | 2
stay_duration | PT1M
reference_count | 1
latitude | 60.0776666666667
longitude | 23.5308666666667
geog | 0101000020E6100000B9DEBBE0E687374052A938FBF0094E40
home_flag | t
notes | new moorage note 3
-[ RECORD 2 ]---+---------------------------------------------------
id | 2
vessel_id | t
name | Norra hamnen
country |
stay_code | 4
stay_duration | PT2M
reference_count | 2
latitude | 59.9768833333333
longitude | 23.4321
geog | 0101000020E6100000029A081B9E6E3740455658830AFD4D40
home_flag | f
notes |
-[ RECORD 3 ]---+---------------------------------------------------
id | 3
vessel_id | t
name | Ekenäs
country | fi
stay_code | 1
stay_duration |
reference_count | 1
latitude | 59.86
longitude | 23.3657666666667
geog | 0101000020E6100000E84C5FE2A25D3740AE47E17A14EE4D40
home_flag | f
notes |
api.moorages_view
-[ RECORD 1 ]-------+---------------------
id | 2
moorage | Norra hamnen
default_stay | Dock
default_stay_id | 4
total_stay | 0
total_duration | PT2M
arrivals_departures | 2
-[ RECORD 2 ]-------+---------------------
id | 1
moorage | patch moorage name 3
default_stay | Anchor
default_stay_id | 2
total_stay | 0
total_duration | PT1M
arrivals_departures | 1

53
tests/sql/monitoring.sql Normal file
View File

@@ -0,0 +1,53 @@
---------------------------------------------------------------------------
-- Listing
--
-- List current database
select current_database();
-- connect to the DB
\c signalk
-- output display format
\x on
\echo 'Set vessel_id and vessel.name'
-- set vessel_id
SELECT v.vessel_id as "vessel_id" FROM auth.vessels v WHERE v.owner_email = 'demo+kapla@openplotter.cloud' \gset
--\echo :"vessel_id"
SELECT set_config('vessel.id', :'vessel_id', false) IS NOT NULL as vessel_id;
-- set name
SELECT v.name as "name" FROM auth.vessels v WHERE v.owner_email = 'demo+kapla@openplotter.cloud' \gset
--\echo :"vessel_id"
SELECT set_config('vessel.name', :'name', false) IS NOT NULL as name;
\echo 'Test monitoring_view for user'
-- Test monitoring for user
--select * from api.monitoring_view;
select count(*) from api.monitoring_view;
\echo 'Test monitoring_view2 for user'
-- Test monitoring for user
--select * from api.monitoring_view2;
select count(*) from api.monitoring_view2;
\echo 'Test monitoring_view3 for user'
-- Test monitoring for user
--select * from api.monitoring_view3;
select count(*) from api.monitoring_view3;
\echo 'Test monitoring_voltage for user'
-- Test monitoring for user
--select * from api.monitoring_voltage;
select count(*) from api.monitoring_voltage;
\echo 'Test monitoring_temperatures for user'
-- Test monitoring for user
--select * from api.monitoring_temperatures;
select count(*) from api.monitoring_temperatures;
\echo 'Test monitoring_humidity for user'
-- Test monitoring for user
--select * from api.monitoring_humidity;
select count(*) from api.monitoring_humidity;

View File

@@ -0,0 +1,38 @@
current_database
------------------
signalk
(1 row)
You are now connected to database "signalk" as user "username".
Expanded display is on.
Set vessel_id and vessel.name
-[ RECORD 1 ]
vessel_id | t
-[ RECORD 1 ]
name | t
Test monitoring_view for user
-[ RECORD 1 ]
count | 1
Test monitoring_view2 for user
-[ RECORD 1 ]
count | 21
Test monitoring_view3 for user
-[ RECORD 1 ]
count | 3736
Test monitoring_voltage for user
-[ RECORD 1 ]
count | 47
Test monitoring_temperatures for user
-[ RECORD 1 ]
count | 120
Test monitoring_humidity for user
-[ RECORD 1 ]
count | 0

26
tests/sql/otp.sql Normal file
View File

@@ -0,0 +1,26 @@
---------------------------------------------------------------------------
-- Listing
--
-- List current database
select current_database();
-- connect to the DB
\c signalk
-- output display format
\x on
--
--
\echo 'Count auth.accounts'
SELECT count(*) from auth.accounts;
\echo 'Settings auth.accounts'
SELECT preferences->'email_notifications' as email_notifications from auth.accounts;
SELECT preferences->'phone_notifications' as phone_notifications from auth.accounts;
SELECT preferences->'telegram'->'chat'->'id' as telegram from auth.accounts;
--SELECT preferences->'telegram'->'date' - INTERVAL 5 minutes from auth.accounts;
SELECT count(*)
FROM auth.accounts
WHERE preferences->'telegram'->'chat'->'id' is null;

30
tests/sql/otp.sql.output Normal file
View File

@@ -0,0 +1,30 @@
current_database
------------------
signalk
(1 row)
You are now connected to database "signalk" as user "username".
Expanded display is on.
Count auth.accounts
-[ RECORD 1 ]
count | 2
Settings auth.accounts
-[ RECORD 1 ]-------+------
email_notifications | false
-[ RECORD 2 ]-------+------
email_notifications | false
-[ RECORD 1 ]-------+------
phone_notifications | false
-[ RECORD 2 ]-------+------
phone_notifications | false
-[ RECORD 1 ]--------
telegram | 1234567890
-[ RECORD 2 ]--------
telegram | 9876543210
-[ RECORD 1 ]
count | 0

90
tests/sql/summary.sql Normal file
View File

@@ -0,0 +1,90 @@
---------------------------------------------------------------------------
-- Listing
--
-- List current database
select current_database();
-- connect to the DB
\c signalk
-- output display format
\x on
-- List PostgreSQL version
--SELECT version();
-- check only version number to remove arch details
SHOW server_version;
-- List Postgis version
SELECT postgis_full_version();
-- List of installed extensions
-- \dx
--SELECT extname,extversion FROM pg_extension;
SELECT e.extname AS "Name", e.extversion AS "Version", n.nspname AS "Schema", c.description AS "Description"
FROM pg_catalog.pg_extension e
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = e.extnamespace
LEFT JOIN pg_catalog.pg_description c ON c.objoid = e.oid AND c.classoid = 'pg_catalog.pg_extension'::pg_catalog.regclass
ORDER BY 1;
-- List of installed extensions available for upgrade
SELECT name, default_version, installed_version FROM pg_available_extensions where default_version <> installed_version;
-- List Language
\echo 'List Language'
SELECT * FROM pg_language;
-- List of databases
-- ICU Missing entry in some system?
--\l
SELECT datname,datconnlimit,datcollate,datctype,datallowconn FROM pg_database;
-- List of relations
\echo 'List of relations'
\dtables
-- List tables from schema api
select t.table_name as schema_api
from information_schema.tables t
where t.table_schema = 'api'
and t.table_type = 'BASE TABLE'
order by t.table_name;
-- List tables from schema public
select t.table_name as schema_public
from information_schema.tables t
where t.table_schema = 'public'
and t.table_type = 'BASE TABLE'
order by t.table_name;
-- List tables from schema auth
select t.table_name as schema_auth
from information_schema.tables t
where t.table_schema = 'auth'
and t.table_type = 'BASE TABLE'
order by t.table_name;
-- List tables from schema jwt
select t.table_name as schema_jwt
from information_schema.tables t
where t.table_schema = 'jwt'
and t.table_type = 'BASE TABLE'
order by t.table_name;
-- List Row Security Policies - todo reduce and improve output
\echo 'List Row Security Policies'
select * from pg_policies;
-- Test functions
\echo 'Test nominatim reverse_geocode_py_fn'
SELECT public.reverse_geocode_py_fn('nominatim', 1.4440116666666667, 38.82985166666667);
\echo 'Test geoip reverse_geoip_py_fn'
--SELECT reverse_geoip_py_fn('62.74.13.231');
-- List details product versions
SELECT api.versions_fn();
SELECT * FROM api.versions_view;
-- List application settings
--SELECT * IS NOT NULl FROM public.app_settings;

View File

@@ -0,0 +1,626 @@
current_database
------------------
signalk
(1 row)
You are now connected to database "signalk" as user "username".
Expanded display is on.
-[ RECORD 1 ]--+-------------------------------
server_version | 15.5 (Debian 15.5-1.pgdg110+1)
-[ RECORD 1 ]--------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
postgis_full_version | POSTGIS="3.4.0 0874ea3" [EXTENSION] PGSQL="150" GEOS="3.9.0-CAPI-1.16.2" PROJ="7.2.1 NETWORK_ENABLED=OFF URL_ENDPOINT=https://cdn.proj.org USER_WRITABLE_DIRECTORY=/var/lib/postgresql/.local/share/proj DATABASE_PATH=/usr/share/proj/proj.db" LIBXML="2.9.10" LIBJSON="0.15" LIBPROTOBUF="1.3.3" WAGYU="0.5.0 (Internal)"
-[ RECORD 1 ]--------------------------------------------------------------------------------------
Name | citext
Version | 1.6
Schema | public
Description | data type for case-insensitive character strings
-[ RECORD 2 ]--------------------------------------------------------------------------------------
Name | jsonb_plpython3u
Version | 1.0
Schema | public
Description | transform between jsonb and plpython3u
-[ RECORD 3 ]--------------------------------------------------------------------------------------
Name | moddatetime
Version | 1.0
Schema | public
Description | functions for tracking last modification time
-[ RECORD 4 ]--------------------------------------------------------------------------------------
Name | pg_stat_statements
Version | 1.10
Schema | public
Description | track planning and execution statistics of all SQL statements executed
-[ RECORD 5 ]--------------------------------------------------------------------------------------
Name | pgcrypto
Version | 1.3
Schema | public
Description | cryptographic functions
-[ RECORD 6 ]--------------------------------------------------------------------------------------
Name | plpgsql
Version | 1.0
Schema | pg_catalog
Description | PL/pgSQL procedural language
-[ RECORD 7 ]--------------------------------------------------------------------------------------
Name | plpython3u
Version | 1.0
Schema | pg_catalog
Description | PL/Python3U untrusted procedural language
-[ RECORD 8 ]--------------------------------------------------------------------------------------
Name | postgis
Version | 3.4.0
Schema | public
Description | PostGIS geometry and geography spatial types and functions
-[ RECORD 9 ]--------------------------------------------------------------------------------------
Name | timescaledb
Version | 2.12.2
Schema | public
Description | Enables scalable inserts and complex queries for time-series data (Community Edition)
-[ RECORD 10 ]-------------------------------------------------------------------------------------
Name | uuid-ossp
Version | 1.1
Schema | public
Description | generate universally unique identifiers (UUIDs)
(0 rows)
List Language
-[ RECORD 1 ]-+-----------
oid | 12
lanname | internal
lanowner | 10
lanispl | f
lanpltrusted | f
lanplcallfoid | 0
laninline | 0
lanvalidator | 2246
lanacl |
-[ RECORD 2 ]-+-----------
oid | 13
lanname | c
lanowner | 10
lanispl | f
lanpltrusted | f
lanplcallfoid | 0
laninline | 0
lanvalidator | 2247
lanacl |
-[ RECORD 3 ]-+-----------
oid | 14
lanname | sql
lanowner | 10
lanispl | f
lanpltrusted | t
lanplcallfoid | 0
laninline | 0
lanvalidator | 2248
lanacl |
-[ RECORD 4 ]-+-----------
oid | 13542
lanname | plpgsql
lanowner | 10
lanispl | t
lanpltrusted | t
lanplcallfoid | 13539
laninline | 13540
lanvalidator | 13541
lanacl |
-[ RECORD 5 ]-+-----------
oid | 18283
lanname | plpython3u
lanowner | 10
lanispl | t
lanpltrusted | t
lanplcallfoid | 18280
laninline | 18281
lanvalidator | 18282
lanacl |
-[ RECORD 1 ]+-----------
datname | postgres
datconnlimit | -1
datcollate | en_US.utf8
datctype | en_US.utf8
datallowconn | t
-[ RECORD 2 ]+-----------
datname | template1
datconnlimit | -1
datcollate | en_US.utf8
datctype | en_US.utf8
datallowconn | t
-[ RECORD 3 ]+-----------
datname | template0
datconnlimit | -1
datcollate | en_US.utf8
datctype | en_US.utf8
datallowconn | f
-[ RECORD 4 ]+-----------
datname | signalk
datconnlimit | 100
datcollate | en_US.utf8
datctype | en_US.utf8
datallowconn | t
List of relations
List of relations
-[ RECORD 1 ]---------------------------------
Schema | public
Name | aistypes
Type | table
Owner | username
-[ RECORD 2 ]---------------------------------
Schema | public
Name | app_settings
Type | table
Owner | username
-[ RECORD 3 ]---------------------------------
Schema | public
Name | badges
Type | table
Owner | username
-[ RECORD 4 ]---------------------------------
Schema | public
Name | email_templates
Type | table
Owner | username
-[ RECORD 5 ]---------------------------------
Schema | public
Name | geocoders
Type | table
Owner | username
-[ RECORD 6 ]---------------------------------
Schema | public
Name | iso3166
Type | table
Owner | username
-[ RECORD 7 ]---------------------------------
Schema | public
Name | mid
Type | table
Owner | username
-[ RECORD 8 ]---------------------------------
Schema | public
Name | ne_10m_geography_marine_polys
Type | table
Owner | username
-[ RECORD 9 ]---------------------------------
Schema | public
Name | ne_10m_geography_marine_polys_gid_seq
Type | sequence
Owner | username
-[ RECORD 10 ]--------------------------------
Schema | public
Name | process_queue
Type | table
Owner | username
-[ RECORD 11 ]--------------------------------
Schema | public
Name | process_queue_id_seq
Type | sequence
Owner | username
-[ RECORD 12 ]--------------------------------
Schema | public
Name | spatial_ref_sys
Type | table
Owner | username
-[ RECORD 1 ]--------
schema_api | logbook
-[ RECORD 2 ]--------
schema_api | metadata
-[ RECORD 3 ]--------
schema_api | metrics
-[ RECORD 4 ]--------
schema_api | moorages
-[ RECORD 5 ]--------
schema_api | stays
-[ RECORD 6 ]--------
schema_api | stays_at
-[ RECORD 1 ]-+------------------------------
schema_public | aistypes
-[ RECORD 2 ]-+------------------------------
schema_public | app_settings
-[ RECORD 3 ]-+------------------------------
schema_public | badges
-[ RECORD 4 ]-+------------------------------
schema_public | email_templates
-[ RECORD 5 ]-+------------------------------
schema_public | geocoders
-[ RECORD 6 ]-+------------------------------
schema_public | iso3166
-[ RECORD 7 ]-+------------------------------
schema_public | mid
-[ RECORD 8 ]-+------------------------------
schema_public | ne_10m_geography_marine_polys
-[ RECORD 9 ]-+------------------------------
schema_public | process_queue
-[ RECORD 10 ]+------------------------------
schema_public | spatial_ref_sys
-[ RECORD 1 ]---------
schema_auth | accounts
-[ RECORD 2 ]---------
schema_auth | otp
-[ RECORD 3 ]---------
schema_auth | vessels
(0 rows)
List Row Security Policies
-[ RECORD 1 ]------------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | metadata
policyname | admin_all
permissive | PERMISSIVE
roles | {username}
cmd | ALL
qual | true
with_check | true
-[ RECORD 2 ]------------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | metadata
policyname | api_vessel_role
permissive | PERMISSIVE
roles | {vessel_role}
cmd | ALL
qual | (vessel_id = current_setting('vessel.id'::text, false))
with_check | true
-[ RECORD 3 ]------------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | metadata
policyname | api_user_role
permissive | PERMISSIVE
roles | {user_role}
cmd | ALL
qual | (vessel_id = current_setting('vessel.id'::text, true))
with_check | (vessel_id = current_setting('vessel.id'::text, false))
-[ RECORD 4 ]------------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | metadata
policyname | api_scheduler_role
permissive | PERMISSIVE
roles | {scheduler}
cmd | ALL
qual | (vessel_id = current_setting('vessel.id'::text, false))
with_check | (vessel_id = current_setting('vessel.id'::text, false))
-[ RECORD 5 ]------------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | metadata
policyname | grafana_role
permissive | PERMISSIVE
roles | {grafana}
cmd | ALL
qual | (vessel_id = current_setting('vessel.id'::text, false))
with_check | false
-[ RECORD 6 ]------------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | metadata
policyname | grafana_proxy_role
permissive | PERMISSIVE
roles | {grafana_auth}
cmd | ALL
qual | true
with_check | false
-[ RECORD 7 ]------------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | metrics
policyname | admin_all
permissive | PERMISSIVE
roles | {username}
cmd | ALL
qual | true
with_check | true
-[ RECORD 8 ]------------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | metrics
policyname | api_vessel_role
permissive | PERMISSIVE
roles | {vessel_role}
cmd | ALL
qual | (vessel_id = current_setting('vessel.id'::text, false))
with_check | true
-[ RECORD 9 ]------------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | metrics
policyname | api_user_role
permissive | PERMISSIVE
roles | {user_role}
cmd | ALL
qual | (vessel_id = current_setting('vessel.id'::text, true))
with_check | (vessel_id = current_setting('vessel.id'::text, false))
-[ RECORD 10 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | metrics
policyname | api_scheduler_role
permissive | PERMISSIVE
roles | {scheduler}
cmd | ALL
qual | (vessel_id = current_setting('vessel.id'::text, false))
with_check | (vessel_id = current_setting('vessel.id'::text, false))
-[ RECORD 11 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | metrics
policyname | grafana_role
permissive | PERMISSIVE
roles | {grafana}
cmd | ALL
qual | (vessel_id = current_setting('vessel.id'::text, false))
with_check | false
-[ RECORD 12 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | metrics
policyname | api_anonymous_role
permissive | PERMISSIVE
roles | {api_anonymous}
cmd | ALL
qual | (vessel_id = current_setting('vessel.id'::text, false))
with_check | false
-[ RECORD 13 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | logbook
policyname | admin_all
permissive | PERMISSIVE
roles | {username}
cmd | ALL
qual | true
with_check | true
-[ RECORD 14 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | logbook
policyname | api_vessel_role
permissive | PERMISSIVE
roles | {vessel_role}
cmd | ALL
qual | (vessel_id = current_setting('vessel.id'::text, false))
with_check | true
-[ RECORD 15 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | logbook
policyname | api_user_role
permissive | PERMISSIVE
roles | {user_role}
cmd | ALL
qual | (vessel_id = current_setting('vessel.id'::text, true))
with_check | (vessel_id = current_setting('vessel.id'::text, false))
-[ RECORD 16 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | logbook
policyname | api_scheduler_role
permissive | PERMISSIVE
roles | {scheduler}
cmd | ALL
qual | (vessel_id = current_setting('vessel.id'::text, false))
with_check | (vessel_id = current_setting('vessel.id'::text, false))
-[ RECORD 17 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | logbook
policyname | grafana_role
permissive | PERMISSIVE
roles | {grafana}
cmd | ALL
qual | (vessel_id = current_setting('vessel.id'::text, false))
with_check | false
-[ RECORD 18 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | logbook
policyname | api_anonymous_role
permissive | PERMISSIVE
roles | {api_anonymous}
cmd | ALL
qual | (vessel_id = current_setting('vessel.id'::text, false))
with_check | false
-[ RECORD 19 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | stays
policyname | admin_all
permissive | PERMISSIVE
roles | {username}
cmd | ALL
qual | true
with_check | true
-[ RECORD 20 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | stays
policyname | api_vessel_role
permissive | PERMISSIVE
roles | {vessel_role}
cmd | ALL
qual | (vessel_id = current_setting('vessel.id'::text, false))
with_check | true
-[ RECORD 21 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | stays
policyname | api_user_role
permissive | PERMISSIVE
roles | {user_role}
cmd | ALL
qual | (vessel_id = current_setting('vessel.id'::text, true))
with_check | (vessel_id = current_setting('vessel.id'::text, false))
-[ RECORD 22 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | stays
policyname | api_scheduler_role
permissive | PERMISSIVE
roles | {scheduler}
cmd | ALL
qual | (vessel_id = current_setting('vessel.id'::text, false))
with_check | (vessel_id = current_setting('vessel.id'::text, false))
-[ RECORD 23 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | stays
policyname | grafana_role
permissive | PERMISSIVE
roles | {grafana}
cmd | ALL
qual | (vessel_id = current_setting('vessel.id'::text, false))
with_check | false
-[ RECORD 24 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | moorages
policyname | admin_all
permissive | PERMISSIVE
roles | {username}
cmd | ALL
qual | true
with_check | true
-[ RECORD 25 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | moorages
policyname | api_vessel_role
permissive | PERMISSIVE
roles | {vessel_role}
cmd | ALL
qual | (vessel_id = current_setting('vessel.id'::text, false))
with_check | true
-[ RECORD 26 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | moorages
policyname | api_user_role
permissive | PERMISSIVE
roles | {user_role}
cmd | ALL
qual | (vessel_id = current_setting('vessel.id'::text, true))
with_check | (vessel_id = current_setting('vessel.id'::text, false))
-[ RECORD 27 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | moorages
policyname | api_scheduler_role
permissive | PERMISSIVE
roles | {scheduler}
cmd | ALL
qual | (vessel_id = current_setting('vessel.id'::text, false))
with_check | (vessel_id = current_setting('vessel.id'::text, false))
-[ RECORD 28 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | moorages
policyname | grafana_role
permissive | PERMISSIVE
roles | {grafana}
cmd | ALL
qual | (vessel_id = current_setting('vessel.id'::text, false))
with_check | false
-[ RECORD 29 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | auth
tablename | vessels
policyname | admin_all
permissive | PERMISSIVE
roles | {username}
cmd | ALL
qual | true
with_check | true
-[ RECORD 30 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | auth
tablename | vessels
policyname | api_user_role
permissive | PERMISSIVE
roles | {user_role}
cmd | ALL
qual | ((vessel_id = current_setting('vessel.id'::text, true)) AND ((owner_email)::text = current_setting('user.email'::text, true)))
with_check | ((vessel_id = current_setting('vessel.id'::text, true)) AND ((owner_email)::text = current_setting('user.email'::text, true)))
-[ RECORD 31 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | auth
tablename | vessels
policyname | grafana_role
permissive | PERMISSIVE
roles | {grafana}
cmd | ALL
qual | ((owner_email)::text = current_setting('user.email'::text, true))
with_check | false
-[ RECORD 32 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | auth
tablename | vessels
policyname | grafana_proxy_role
permissive | PERMISSIVE
roles | {grafana_auth}
cmd | ALL
qual | true
with_check | false
-[ RECORD 33 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | auth
tablename | accounts
policyname | admin_all
permissive | PERMISSIVE
roles | {username}
cmd | ALL
qual | true
with_check | true
-[ RECORD 34 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | auth
tablename | accounts
policyname | api_user_role
permissive | PERMISSIVE
roles | {user_role}
cmd | ALL
qual | ((email)::text = current_setting('user.email'::text, true))
with_check | ((email)::text = current_setting('user.email'::text, true))
-[ RECORD 35 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | auth
tablename | accounts
policyname | api_scheduler_role
permissive | PERMISSIVE
roles | {scheduler}
cmd | ALL
qual | ((email)::text = current_setting('user.email'::text, true))
with_check | ((email)::text = current_setting('user.email'::text, true))
-[ RECORD 36 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | auth
tablename | accounts
policyname | grafana_proxy_role
permissive | PERMISSIVE
roles | {grafana_auth}
cmd | ALL
qual | true
with_check | false
-[ RECORD 37 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | public
tablename | process_queue
policyname | admin_all
permissive | PERMISSIVE
roles | {username}
cmd | ALL
qual | true
with_check | true
-[ RECORD 38 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | public
tablename | process_queue
policyname | api_vessel_role
permissive | PERMISSIVE
roles | {vessel_role}
cmd | ALL
qual | ((ref_id = current_setting('user.id'::text, true)) OR (ref_id = current_setting('vessel.id'::text, true)))
with_check | true
-[ RECORD 39 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | public
tablename | process_queue
policyname | api_user_role
permissive | PERMISSIVE
roles | {user_role}
cmd | ALL
qual | ((ref_id = current_setting('user.id'::text, true)) OR (ref_id = current_setting('vessel.id'::text, true)))
with_check | ((ref_id = current_setting('user.id'::text, true)) OR (ref_id = current_setting('vessel.id'::text, true)))
-[ RECORD 40 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | public
tablename | process_queue
policyname | api_scheduler_role
permissive | PERMISSIVE
roles | {scheduler}
cmd | ALL
qual | true
with_check | false
Test nominatim reverse_geocode_py_fn
-[ RECORD 1 ]---------+----------------------------------------
reverse_geocode_py_fn | {"name": "Spain", "country_code": "es"}
Test geoip reverse_geoip_py_fn
-[ RECORD 1 ]----------------------------------------------------------------------------------------------------------------------------------------------
versions_fn | {"api_version" : "0.5.0", "sys_version" : "PostgreSQL 15.5", "timescaledb" : "2.12.2", "postgis" : "3.4.0", "postgrest" : "PostgREST 11.2.2"}
-[ RECORD 1 ]-----------------
api_version | 0.5.0
sys_version | PostgreSQL 15.5
timescaledb | 2.12.2
postgis | 3.4.0
postgrest | PostgREST 11.2.2

48
tests/sql/telegram.sql Normal file
View File

@@ -0,0 +1,48 @@
---------------------------------------------------------------------------
-- Listing
--
-- List current database
select current_database();
-- connect to the DB
\c signalk
-- output display format
\x on
--
-- telegram
SET ROLE username;
-- Does chat id session exist?
SELECT auth.telegram_session_exists_fn(1234567890);
SELECT auth.telegram_session_exists_fn(9876543210);
SELECT auth.telegram_session_exists_fn(1472583690);
-- Assign vessel_id var
SELECT v.vessel_id as "vessel_id_kapla" FROM auth.vessels v WHERE v.owner_email = 'demo+kapla@openplotter.cloud' \gset
SELECT v.vessel_id as "vessel_id_aava" FROM auth.vessels v WHERE v.owner_email = 'demo+aava@openplotter.cloud' \gset
SET ROLE api_anonymous;
SELECT api.telegram(1234567890::BIGINT) IS NOT NULL as telegram_session;
SELECT api.telegram(9876543210::BIGINT) IS NOT NULL as telegram_session;
SELECT api.telegram(1472583690::BIGINT) IS NULL as telegram_session;
SET ROLE user_role;
SET "user.email" = 'demo+kapla@openplotter.cloud';
--SET vessel.id = 'f94e995cf4d3';
SELECT set_config('vessel.id', :'vessel_id_kapla', false) IS NOT NULL as vessel_id;
SET vessel.name = 'kapla';
--SET vessel.client_id = 'vessels.urn:mrn:imo:mmsi:123456789';
--SELECT * FROM api.vessels_view v;
SELECT name, mmsi, created_at IS NOT NULL as created_at, last_contact IS NOT NULL as last_contact FROM api.vessels_view v;
SELECT name,geojson,watertemperature,insidetemperature,outsidetemperature FROM api.monitoring_view m;
SET "user.email" = 'demo+aava@openplotter.cloud';
SELECT set_config('vessel.id', :'vessel_id_aava', false) IS NOT NULL as vessel_id;
--SET vessel.id = '341dcfa30afb';
SET vessel.name = 'aava';
--SET vessel.client_id = 'vessels.urn:mrn:imo:mmsi:787654321';
--SELECT * FROM api.vessels_view v;
SELECT name, mmsi, created_at IS NOT NULL as created_at, last_contact IS NOT NULL as last_contact FROM api.vessels_view v;
SELECT name,geojson,watertemperature,insidetemperature,outsidetemperature FROM api.monitoring_view m;

View File

@@ -0,0 +1,64 @@
current_database
------------------
signalk
(1 row)
You are now connected to database "signalk" as user "username".
Expanded display is on.
SET
-[ RECORD 1 ]--------------+--
telegram_session_exists_fn | f
-[ RECORD 1 ]--------------+--
telegram_session_exists_fn | f
-[ RECORD 1 ]--------------+--
telegram_session_exists_fn | f
SET
-[ RECORD 1 ]----+--
telegram_session | f
-[ RECORD 1 ]----+--
telegram_session | f
-[ RECORD 1 ]----+--
telegram_session | t
SET
SET
-[ RECORD 1 ]
vessel_id | t
SET
-[ RECORD 1 ]+------
name | kapla
mmsi |
created_at | t
last_contact | t
-[ RECORD 1 ]------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
name | kapla
geojson | {"type": "Feature", "geometry": {"type": "Point", "coordinates": [23.365766667, 59.86]}, "properties": {"name": "kapla", "latitude": 59.86, "longitude": 23.365766666666666}}
watertemperature |
insidetemperature |
outsidetemperature |
SET
-[ RECORD 1 ]
vessel_id | t
SET
-[ RECORD 1 ]+----------
name | aava
mmsi | 787654321
created_at | t
last_contact | t
-[ RECORD 1 ]------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
name | aava
geojson | {"type": "Feature", "geometry": {"type": "Point", "coordinates": [2.2934791, 41.465333283]}, "properties": {"name": "aava", "latitude": 41.46533328333334, "longitude": 2.2934791}}
watertemperature | 280.25
insidetemperature |
outsidetemperature |

186
tests/tests.sh Normal file
View File

@@ -0,0 +1,186 @@
# PostgSail Unit test
if [[ -z "${PGSAIL_DB_URI}" ]]; then
echo "PGSAIL_DB_URI is undefined"
exit 1
fi
if [[ -z "${PGSAIL_API_URI}" ]]; then
echo "PGSAIL_API_URI is undefined"
exit 1
fi
# go install
if [[ ! -x "/usr/bin/go" || ! -x "/root/go/bin/mermerd" ]]; then
#wget -q https://go.dev/dl/go1.21.4.linux-arm64.tar.gz && \
#rm -rf /usr/local/go && tar -C /usr/local -xzf go1.21.4.linux-arm64.tar.gz && \
apt update && apt -y install golang && \
go install github.com/KarnerTh/mermerd@latest
fi
# pnpm install
if [[ ! -x "/usr/local/bin/pnpm" ]]; then
npm install -g pnpm
fi
pnpm install || exit 1
# settings
export mymocha="./node_modules/mocha/bin/_mocha"
mkdir -p output/ && rm -rf output/*
$mymocha index.js --reporter ./node_modules/mochawesome --reporter-options reportDir=output/,reportFilename=report1.html
if [ $? -eq 0 ]; then
echo OK
else
echo mocha index.js
exit 1
fi
$mymocha index2.js --reporter ./node_modules/mochawesome --reporter-options reportDir=output/,reportFilename=report2.html
if [ $? -eq 0 ]; then
echo OK
else
echo mocha index2.js
exit 1
fi
# https://www.postgresql.org/docs/current/app-psql.html
# run cron jobs
#psql -U ${POSTGRES_USER} -h 172.30.0.1 signalk < sql/cron_run_jobs.sql > output/cron_run_jobs.sql.output
psql ${PGSAIL_DB_URI} < sql/cron_run_jobs.sql > output/cron_run_jobs.sql.output
diff sql/cron_run_jobs.sql.output output/cron_run_jobs.sql.output > /dev/null
#diff -u sql/cron_run_jobs.sql.output output/cron_run_jobs.sql.output | wc -l
#echo 0
if [ $? -eq 0 ]; then
echo OK
else
echo SQL cron_run_jobs.sql FAILED
diff -u sql/cron_run_jobs.sql.output output/cron_run_jobs.sql.output
exit 1
fi
# handle post processing
#psql -U ${POSTGRES_USER} -h 172.30.0.1 signalk < sql/cron_post_jobs.sql > output/cron_post_jobs.sql.output
psql ${PGSAIL_DB_URI} < sql/cron_post_jobs.sql > output/cron_post_jobs.sql.output
diff sql/cron_post_jobs.sql.output output/cron_post_jobs.sql.output > /dev/null
#diff -u sql/cron_post_jobs.sql.output output/cron_post_jobs.sql.output | wc -l
#echo 0
if [ $? -eq 0 ]; then
echo OK
else
echo SQL cron_post_jobs.sql FAILED
diff -u sql/cron_post_jobs.sql.output output/cron_post_jobs.sql.output
exit 1
fi
$mymocha index3.js --reporter ./node_modules/mochawesome --reporter-options reportDir=output/,reportFilename=report3.html
#echo 0
if [ $? -eq 0 ]; then
echo OK
else
echo mocha index3.js
exit 1
fi
# Grafana Auth Proxy and role unit tests
psql ${PGSAIL_DB_URI} < sql/grafana.sql > output/grafana.sql.output
diff sql/grafana.sql.output output/grafana.sql.output > /dev/null
#diff -u sql/grafana.sql.output output/grafana.sql.output | wc -l
#echo 0
if [ $? -eq 0 ]; then
echo OK
else
echo SQL grafana.sql FAILED
diff -u sql/grafana.sql.output output/grafana.sql.output
exit 1
fi
# Telegram and role unit tests
psql ${PGSAIL_DB_URI} < sql/telegram.sql > output/telegram.sql.output
diff sql/telegram.sql.output output/telegram.sql.output > /dev/null
#diff -u sql/telegram.sql.output output/telegram.sql.output | wc -l
#echo 0
if [ $? -eq 0 ]; then
echo OK
else
echo SQL telegram.sql FAILED
diff -u sql/telegram.sql.output output/telegram.sql.output
exit 1
fi
# Badges unit tests
psql ${PGSAIL_DB_URI} < sql/badges.sql > output/badges.sql.output
diff sql/badges.sql.output output/badges.sql.output > /dev/null
#diff -u sql/badges.sql.output output/badges.sql.output | wc -l
#echo 0
if [ $? -eq 0 ]; then
echo OK
else
echo SQL badges.sql FAILED
diff -u sql/badges.sql.output output/badges.sql.output
exit
fi
# Summary unit tests
psql ${PGSAIL_DB_URI} < sql/summary.sql > output/summary.sql.output
diff sql/summary.sql.output output/summary.sql.output > /dev/null
#diff -u sql/summary.sql.output output/summary.sql.output | wc -l
#echo 0
if [ $? -eq 0 ]; then
echo OK
else
echo SQL summary.sql FAILED
diff -u sql/summary.sql.output output/summary.sql.output
exit 1
fi
$mymocha index4.js --reporter ./node_modules/mochawesome --reporter-options reportDir=output/,reportFilename=report4.html
if [ $? -eq 0 ]; then
echo OK
else
echo mocha index4.js
exit 1
fi
$mymocha index5.js --reporter ./node_modules/mochawesome --reporter-options reportDir=output/,reportFilename=report5.html
if [ $? -eq 0 ]; then
echo OK
else
echo mocha index5.js
exit 1
fi
# Monitoring unit tests
psql ${PGSAIL_DB_URI} < sql/monitoring.sql > output/monitoring.sql.output
diff sql/monitoring.sql.output output/monitoring.sql.output > /dev/null
#diff -u sql/monitoring.sql.output output/monitoring.sql.output | wc -l
#echo 0
if [ $? -eq 0 ]; then
echo SQL monitoring.sql OK
else
echo SQL monitoring.sql FAILED
diff -u sql/monitoring.sql.output output/monitoring.sql.output
exit 1
fi
# Download and update openapi documentation
wget ${PGSAIL_API_URI} -O openapi.json
#echo 0
if [ $? -eq 0 ]; then
cp openapi.json ../openapi.json
echo openapi.json OK
else
echo openapi.json FAILED
exit 1
fi
# Generate and update mermaid schema documentation
/root/go/bin/mermerd --runConfig ../ERD/mermerdConfig.yaml
echo $?
echo 0
if [ $? -eq 0 ]; then
cp postgsail.md ../ERD/postgsail.md
echo postgsail.md OK
else
echo postgsail.md FAILED
exit 1
fi