@rhizomatics/signalk-einklabel-plugin 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,12 @@
1
+ # 0.4.0
2
+ - Added `einklabel` as a `source` for template fields, and `repainted` as path
3
+ - Added `local_datetime_short` as a datetime format option, for `27 Jun 26 18:05` style output
4
+ - Add Last Repainted field to the Tide Clock example
5
+ # 0.3.2
6
+ - Renamed to signalk-einklabel-plugin
7
+ - Added core test suite
8
+ # 0.3.1
9
+ - Correct name, and include changelog
10
+ # 0.3.0
11
+ - First published beta release
12
+ - Tested publishing Tide Clock on interval to a Zhunyco 3.7" display
package/README.md ADDED
@@ -0,0 +1,129 @@
1
+ # ESL for SignalK
2
+
3
+ ** BETA - basic functionality, limited vendor/product support **
4
+
5
+ A SignalK plugin to display data from SignalK paths, APIs and plugins on Electronic Shelf Labels over a Bluetooth Low Energy (BLE) connection.
6
+
7
+ Electronic Shelf Labels (ESLs) are [eInk](https://en.wikipedia.org/wiki/E_Ink) devices that consume very little battery energy, presuming they are not constantly updated - the battery is used only when the display changes, and a periodic BLE check for incoming changes. Perfect for info that changes only once or twice a day, like tidal information.
8
+
9
+ Since they are designed to be used in large quantity in small shops, they are cheap and simple devices. Earlier models required dedicated controllers, or updates over Wifi or NFC, whereas many modern ones are standalone BLE devices that can be updated from a phone or server.
10
+
11
+ Unlike some eInk projects, this plugin doesn't require any physical modification to the labels, or loading any new firmware. It can send an image to a supported shelf label fresh out of the box.
12
+
13
+ ## Examples
14
+
15
+ ### Tide Clock
16
+
17
+ ![Tide Clock](docs/assets/screenshots/example_tidal_clock.png)
18
+
19
+ The tide clock needs the [signalk-tides](https://github.com/openwatersio/signalk-tides) plugin to be installed and publishing tides to the Resources API. It can however be customized to run with other APIs or take data only from SignalK data paths.
20
+
21
+ ## Templating
22
+
23
+ Templates are simply SVG files, to which expressions can be added to use SignalK data, with options to make it easier to read, like rounding or simplifying dates and times. The template can have sample data in the placeholder, so is easy to layout and visualize.
24
+
25
+ ### Template Source Specification
26
+
27
+ In the `description` of the SVG text box, use a comma separated set of key value pairs to define the data source and formatting.
28
+
29
+ #### SignalK Paths
30
+
31
+ For example, `path=environment.forecast.description` uses the default data source (the `self` vessel context) and the named SignalK path. A bare path with no key/value pairs at all, e.g. just `environment.forecast.description`, is shorthand for the same thing. Overriding the default context can be done with `path=environment.forecast.description,context=vessels.urn:mrn:imo:mmsi:232345678` - the `context` value must match a real SignalK context exactly as it appears in the Data Browser.
32
+
33
+ #### SignalK REST APIs
34
+
35
+ The source can be overridden to use the SignalK server's Resources API instead. For example, `source=resources,resource=tides,path=station.name` picks the `tides` resource and pulls the `station.name` path out of the JSON response - this works for any resource type (`tides`, `waypoints`, `routes`, ...), and needs nothing configured: the plugin reaches the Resources API directly. Where a resource is specified, it will be fetched once for that render, and subsequent fields sourced from the same resource use that cached response.
36
+
37
+ #### Plugin Data
38
+
39
+ `source=einklabel` reads data injected by the plugin itself, rather than from SignalK. Currently the only such value is `path=repainted`, the timestamp of the current repaint - for example `source=einklabel,path=repainted,format=local_datetime_short` to show when the label was last updated.
40
+
41
+ #### Customizing Output
42
+
43
+ A `format` can be specified to make the value easier to understand. The supported formats are:
44
+
45
+ * `local_time` - reduce a time stamp to just the time (H:M:S), omitting the date, and applying daylight savings if appropriate
46
+ * `day_mon` - reduce a time stamp to day and month, e.g. `27 Jun`, applying daylight savings if appropriate
47
+ * `local_datetime_short` - format a time stamp as day, abbreviated month, 2-digit year and 24h time, e.g. `21 Jun 26 18:05`, applying daylight savings if appropriate
48
+ * `utc_offset` - Show a timezone in `UTC+01:00` style format
49
+ * `position` - Format a `{ latitude, longitude }` value as decimal degrees with hemisphere letters, e.g. `56.6250°N 6.0700°W`
50
+ * `raw` - Don't apply automatic SignalK unit conversion and symbol display (see below)
51
+
52
+ SignalK's unit preferences are used to automatically convert a `signalk`-sourced numeric value to its preferred display unit, and append a unit symbol like `kt` or `m`, unless `format=raw` is specified to switch that off. However, when using plugin or API data there may be no path metadata to convert from (for example `signalk-tides` publishes tide data to the Resources API, and `level` is a raw metre value with no SignalK path of its own) - in these cases an explicit `category` can be given instead, and the unit preferences will be applied the same way, for example `category=depth` for the tides level figure.
53
+
54
+ Common categories:
55
+
56
+ * `depth` - Use the SignalK preferred depth unit, make the conversion if needed, and tack on the unit name as a suffix
57
+ * `speed` - Use the SignalK preferred speed unit, make the conversion if needed, and tack on the unit name as a suffix
58
+ * `temperature` - Use the SignalK preferred temperature unit, make the conversion if needed, and tack on the unit name as a suffix
59
+
60
+ Additionally, `round=n` can be used to round to limited decimal places.
61
+
62
+ These can all be combined as in `source=resources,resource=tides,path=extremes[2].level,category=depth,round=2`
63
+
64
+ ### Fonts
65
+
66
+ Three font types are loaded by default, use the font name or simply 'sans-serif' in the SVG editor and choose size and weight (bold, semi-bold etc). Some labels will make a decent attempt to gray scale. Use the simple pure red, yellow, white, black to match the label's limited colour choice (some labels only offer black and white, or black/white/red).
67
+
68
+ * `serif` - Playfair Display
69
+ * `sans-serif` - Roboto
70
+ * `monospace` - JetBrains Mono
71
+
72
+ ## Command Line Interface
73
+
74
+ To get fast feedback on templates and shelf devices without updating and configuring SignalK, a CLI is provided that has these commands.
75
+
76
+ - `vendors` - list supported vendors
77
+ - `scan` - report supported devices found from a BLE scan
78
+
79
+ See also the commands useful for debugging under [Developing Templates]
80
+ - `render` - transform an SVG template and data into a PNG
81
+ - `paint` - render an SVG template and data to a selected ESL
82
+
83
+ ## Vendors
84
+
85
+ ### Zhsunyco
86
+
87
+ Also known as 'Suny'
88
+
89
+ - [BLE ESLs](https://www.zhsunyco.com/digital-display-solution-for-small-retail-business/ble-esl-solution/)
90
+ - The range of labels available on retail sites like AliExpress may be larger than on their corporate site
91
+ - In mid 2026, a 4 colour (BWRY) 3.7" label retailed for about $35, with quantity discounts for bulk sets
92
+ - Cheapest units are 2 colour 1.54", and they go up to 7.5"
93
+
94
+ Python code for a variety of their labels at https://github.com/roxburghm/zhsunyco-esl and https://github.com/NickWaterton/Wolink
95
+
96
+ ## Architecture
97
+
98
+ The primary things managed and provided by the plugin are:
99
+
100
+ * ESL Vendor
101
+ * ESL Device
102
+ * SVG Template
103
+ * SignalK API base URL
104
+ - Used for automatic unit conversion on `signalk`-sourced numeric values and for resolving an explicit `category=` binding - neither has an in-process equivalent, both go via this server's own REST API
105
+ - Optional: left blank, the plugin probes the probable values in likelihood order at startup - `http://localhost:3000`, `http://localhost`, `https://localhost`. Set it explicitly to skip probing
106
+ - Either way, errors clearly if nothing responds (wrong port) or the probe is rejected (anonymous read access not enabled) - these endpoints must allow anonymous read access, since the plugin has no login flow
107
+
108
+ ### Extending
109
+
110
+ #### Hardware
111
+
112
+ Additional vendors and devices can be added by a separate npm package that implements the `VendorDriver` interface and registers itself - there's no scanning of installed packages, registration is always an explicit call by the extension's own code.
113
+
114
+ - `import esl from '@rhizomatics/signalk-einklabel-plugin'; esl.registerVendorDriver(myDriver)`
115
+ - In the SignalK runtime, call this from the extension's own plugin `start()`. In the CLI, load the extension with `esl-cli --require <module> <command>`.
116
+ - Declare this package as a `peerDependency` (not a regular dependency) in the extension package, so npm resolves a single shared copy of the registry.
117
+
118
+ #### Developing Templates
119
+
120
+ Templates can be added to the configurable directory. [Inkscape](https://inkscape.org) free, open source, and recommended for editing templates, or your own favourite editor, or by hand in a text editor for hard core (or just tidying up the template side).
121
+
122
+ Inkscape adds its own metadata to images, which can be stripped off by exporting a simple SVG, although can be left in place with no harm; main reason to simplify the SVG is manual changes in a text editor.
123
+
124
+ The `esl-cli` can be used to debug and validate templates quickly:
125
+
126
+ * `render` - Render templates with SignalK data and write to a local PNG file
127
+ * `paint` - Render templates with SignalK data and send to selected ESL device
128
+ * `fields` - List the fields in the template, with the source specification and the rendered data value
129
+ * `field` - Accept a source specification (outside of any template context) and return the rendered value if available
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "@rhizomatics/signalk-einklabel-plugin",
3
+ "version": "0.4.0",
4
+ "description": "SignalK plugin that renders selected SignalK data to eInk Electronic Shelf Labels",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "bin": {
8
+ "esl-cli": "dist/cli/index.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "templates",
13
+ "CHANGELOG.md",
14
+ "README.md"
15
+ ],
16
+ "scripts": {
17
+ "build": "tsc",
18
+ "watch": "tsc --watch",
19
+ "cli": "ts-node src/cli/index.ts",
20
+ "test": "node --require ts-node/register --test \"src/**/*.test.ts\""
21
+ },
22
+ "signalk":{
23
+ "displayName":"eInk ESL Instruments",
24
+ "appIcon": "/docs/assets/icons/icon_tmp.png",
25
+ "screenshots": [
26
+ "docs/assets/screenshots/example_tidal_clock.png"
27
+ ]
28
+ },
29
+ "keywords": [
30
+ "signalk-node-server-plugin",
31
+ "signalk-category-instruments",
32
+ "signalk-category-hardware",
33
+ "signalk",
34
+ "eink",
35
+ "esl",
36
+ "display",
37
+ "instrument",
38
+ "ble"
39
+ ],
40
+ "engines": {
41
+ "node": ">=18"
42
+ },
43
+ "dependencies": {
44
+ "@fontsource/jetbrains-mono": "^5.2.8",
45
+ "@fontsource/playfair-display": "^5.2.8",
46
+ "@fontsource/roboto": "^5.2.10",
47
+ "@resvg/resvg-wasm": "^2.6.2",
48
+ "@xmldom/xmldom": "^0.9.10",
49
+ "commander": "^12.1.0",
50
+ "luxon": "^3.7.2",
51
+ "mathjs": "^15.2.0",
52
+ "node-ble": "^1.13.0",
53
+ "pngjs": "^7.0.0"
54
+ },
55
+ "devDependencies": {
56
+ "@signalk/server-api": "^2.28.0",
57
+ "@types/luxon": "^3.7.1",
58
+ "@types/node": "^20.14.0",
59
+ "@types/pngjs": "^6.0.5",
60
+ "ts-node": "^10.9.2",
61
+ "typescript": "^5.5.0"
62
+ },
63
+ "license": "APACHE-2.0",
64
+ "repository": {
65
+ "type": "git",
66
+ "url": "git+https://github.com/rhizomatics/signalk-einklabel-plugin.git"
67
+ },
68
+ "author": "jey burrows",
69
+ "bugs": {
70
+ "url": "https://github.com/rhizomatics/signalk-einklabel-plugin/issues"
71
+ },
72
+ "homepage": "https://github.com/rhizomatics/signalk-einklabel-plugin#readme"
73
+ }
@@ -0,0 +1,230 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <svg
3
+ width="416"
4
+ height="240"
5
+ viewBox="0 0 416 240"
6
+ version="1.1"
7
+ id="svg1"
8
+ sodipodi:docname="tide.svg"
9
+ inkscape:version="1.3.2 (091e20e, 2023-11-25)"
10
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
11
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
12
+ xmlns="http://www.w3.org/2000/svg"
13
+ xmlns:svg="http://www.w3.org/2000/svg">
14
+ <defs
15
+ id="defs1" />
16
+ <sodipodi:namedview
17
+ id="namedview1"
18
+ pagecolor="#ffffff"
19
+ bordercolor="#000000"
20
+ borderopacity="0.25"
21
+ inkscape:showpageshadow="2"
22
+ inkscape:pageopacity="0.0"
23
+ inkscape:pagecheckerboard="0"
24
+ inkscape:deskcolor="#d1d1d1"
25
+ inkscape:zoom="0.98333333"
26
+ inkscape:cx="250.16949"
27
+ inkscape:cy="246.61017"
28
+ inkscape:window-width="1488"
29
+ inkscape:window-height="674"
30
+ inkscape:window-x="0"
31
+ inkscape:window-y="33"
32
+ inkscape:window-maximized="0"
33
+ inkscape:current-layer="svg1" />
34
+ <rect
35
+ x="1.0169492"
36
+ y="2.0798673e-08"
37
+ width="416"
38
+ height="240"
39
+ fill="#ffffff"
40
+ id="rect1" />
41
+ <rect
42
+ style="fill:#ffff00;stroke:#000000;stroke-width:0.614045"
43
+ id="rect2"
44
+ width="416.94916"
45
+ height="18.410578"
46
+ x="-24.928873"
47
+ y="-242.40596"
48
+ transform="matrix(1,0,-0.1068988,-0.99426991,0,0)" />
49
+ <text
50
+ id="station.name"
51
+ x="11.235578"
52
+ y="26.851856"
53
+ font-size="18"
54
+ font-family="serif"
55
+ fill="yellow"
56
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:32px;font-family:serif;-inkscape-font-specification:'serif, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#ffff00;stroke:#000000;stroke-width:1.2278;stroke-opacity:1"
57
+ transform="scale(0.89274878,1.1201359)">Tobermory<desc
58
+ id="desc1">source=resources,resource=tides,path=station.name</desc></text>
59
+ <text
60
+ id="station.name-8"
61
+ x="325.49939"
62
+ y="195.87753"
63
+ font-size="18"
64
+ font-family="serif"
65
+ fill="black"
66
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#999999;stroke-width:2.39571"
67
+ transform="scale(0.83037506,1.2042751)"><tspan
68
+ sodipodi:role="line"
69
+ id="tspan1"
70
+ x="325.49939"
71
+ y="195.87753"
72
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:2.39571;fill:#999999">signalk-einklabel-plugin</tspan></text>
73
+ <text
74
+ id="station.name-8-2"
75
+ x="4.3477793"
76
+ y="201.9895"
77
+ font-size="18"
78
+ font-family="serif"
79
+ fill="black"
80
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:13.3333px;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;stroke-width:1.19309"
81
+ transform="scale(0.85412558,1.170788)"><desc
82
+ id="desc12">source=einklabel,path=repainted,format=local_datetime_short</desc><tspan
83
+ sodipodi:role="line"
84
+ id="tspan3"
85
+ x="4.3477793"
86
+ y="201.9895"
87
+ style="stroke-width:1.19309;-inkscape-font-specification:'Sans, Normal';font-family:Sans;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:13.33333347px;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal">Last updated 27 Jun 26 18:05</tspan></text>
88
+ <text
89
+ id="extremes.0"
90
+ x="7.9661016"
91
+ y="60"
92
+ font-size="14"
93
+ font-family="monospace"
94
+ fill="black"
95
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:18.6667px;font-family:monospace;-inkscape-font-specification:'monospace, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#ff0000">High Water</text>
96
+ <text
97
+ id="extremes.1"
98
+ x="13.35283"
99
+ y="118.57627"
100
+ font-size="14"
101
+ font-family="monospace"
102
+ fill="black"
103
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:18.6667px;font-family:monospace;-inkscape-font-specification:'monospace, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#ff0000">Low Water</text>
104
+ <text
105
+ id="extremes.2"
106
+ x="7.9661016"
107
+ y="177.55931"
108
+ font-size="14"
109
+ font-family="monospace"
110
+ fill="red"
111
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:18.6667px;font-family:monospace;-inkscape-font-specification:'monospace, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#ff0000">High Water</text>
112
+ <text
113
+ id="environment.time.timezoneRegion"
114
+ x="338.47461"
115
+ y="17.457626"
116
+ font-size="12px"
117
+ font-family="sans-serif"
118
+ fill="#ffff00"
119
+ style="fill:#000000">UTC+01:00<desc
120
+ id="desc11">path=environment.time.timezoneRegion,format=utc_offset</desc></text>
121
+ <text
122
+ id="extremes.2.level"
123
+ x="173.64978"
124
+ y="199.27094"
125
+ font-size="14"
126
+ font-family="monospace"
127
+ fill="red"
128
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:32px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;stroke-width:1.44101"
129
+ transform="scale(0.95993086,1.0417417)"
130
+ inkscape:transform-center-x="11.578901"
131
+ inkscape:transform-center-y="-62.5909"><tspan
132
+ sodipodi:role="line"
133
+ id="tspan2-0"
134
+ x="173.64978"
135
+ y="199.27094"
136
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:32px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:1.44101">3.3m</tspan><desc
137
+ id="desc6">source=resources,resource=tides,path=extremes.[2].level,category=depth</desc></text>
138
+ <text
139
+ id="extremes.2.time"
140
+ x="31.471279"
141
+ y="166.20743"
142
+ font-size="14"
143
+ font-family="monospace"
144
+ fill="red"
145
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:32px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;stroke-width:1.31448"
146
+ transform="scale(0.77910527,1.2835236)">15:53<desc
147
+ id="desc10">source=resources,resource=tides,path=extremes.[2].time,format=local_time</desc></text>
148
+ <text
149
+ id="extremes.1.level"
150
+ x="182.71886"
151
+ y="135.3521"
152
+ font-size="14"
153
+ font-family="monospace"
154
+ fill="red"
155
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:32px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;stroke-width:1.40832"
156
+ transform="scale(0.90784041,1.1015152)"
157
+ inkscape:transform-center-y="-96.23206"
158
+ inkscape:transform-center-x="-0.00043342573"><tspan
159
+ sodipodi:role="line"
160
+ id="tspan2-1"
161
+ x="182.71886"
162
+ y="135.3521"
163
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:32px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:1.40832">1.6m</tspan><desc
164
+ id="desc5">source=resources,resource=tides,path=extremes.[1].level,category=depth</desc></text>
165
+ <text
166
+ id="extremes.1.time"
167
+ x="33.328102"
168
+ y="118.54723"
169
+ font-size="14"
170
+ font-family="monospace"
171
+ fill="red"
172
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:32px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;stroke-width:1.31609"
173
+ transform="scale(0.77081545,1.2973274)">09:36<desc
174
+ id="desc8">source=resources,resource=tides,path=extremes.[1].time,format=local_time</desc></text>
175
+ <text
176
+ id="extremes.0.time"
177
+ x="29.798925"
178
+ y="79.890121"
179
+ font-size="14"
180
+ font-family="monospace"
181
+ fill="red"
182
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:32px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;stroke-width:1.32646"
183
+ transform="scale(0.82428216,1.2131768)">03:27<desc
184
+ id="desc3">source=resources,resource=tides,path=extremes.[0].time,format=local_time</desc></text>
185
+ <text
186
+ id="extremes.0.time-8"
187
+ x="378.11157"
188
+ y="79.890121"
189
+ font-size="14"
190
+ font-family="monospace"
191
+ fill="red"
192
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:32px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#666666;stroke-width:1.32646"
193
+ transform="scale(0.82428216,1.2131768)"><desc
194
+ id="desc2">source=resources,resource=tides,path=extremes.[0].time,format=day_mon</desc>27 Jun</text>
195
+ <text
196
+ id="extremes.0.time-8-5"
197
+ x="378.11157"
198
+ y="125.12273"
199
+ font-size="14"
200
+ font-family="monospace"
201
+ fill="red"
202
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:32px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#666666;stroke-width:1.32646"
203
+ transform="scale(0.82428216,1.2131768)"><desc
204
+ id="desc7">source=resources,resource=tides,path=extremes.[1].time,format=day_mon</desc>27 Jun</text>
205
+ <text
206
+ id="extremes.0.time-8-9"
207
+ x="378.11157"
208
+ y="174.46791"
209
+ font-size="14"
210
+ font-family="monospace"
211
+ fill="red"
212
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:32px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#666666;stroke-width:1.32646"
213
+ transform="scale(0.82428216,1.2131768)"><desc
214
+ id="desc9">source=resources,resource=tides,path=extremes.[2].time,format=day_mon</desc>27 Jun</text>
215
+ <text
216
+ id="extremes.0.level"
217
+ x="173.7571"
218
+ y="88.829666"
219
+ font-size="14"
220
+ font-family="monospace"
221
+ fill="red"
222
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:32px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;stroke-width:1.38653"
223
+ transform="scale(0.95553786,1.046531)"><tspan
224
+ sodipodi:role="line"
225
+ id="tspan2"
226
+ x="173.7571"
227
+ y="88.829666"
228
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:32px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:1.38653">3.4m</tspan><desc
229
+ id="desc4">source=resources,resource=tides,path=extremes.[0].level,category=depth</desc></text>
230
+ </svg>