Package: velotrain
velotrainvelotrain reads raw transponder passing information from a set
of up to 9 measurement points, filters the messages according
to track configuration parameters and then emits valid timing
measurements as JSON-encoded objects to MQTT.OperationThe velodrome is divided into a closed loop of timing sectors
by measurement points at each of the interesting offsets, for example:Finish line200m StartPursuit BPursuit AA rider is considered to be in a 'run' if their speed over any
configured sector (eg Finish line to 200m start) is between
configured values (default: 38 km/h to 90 km/h).
Passings emitted for a rider in a run will include any available
lap, half-lap, quarter-lap, 100 m, and 50 m splits.
Rider passings slower or faster than the configured limits are
considered isolated, and will be reported without splits.Where possible, valid raw passings received out of order will be
corrected and re-ordered before being emitted. In the case that
an isolated raw passing violates the configured track ordering and
speed limits, it may be reported out of order.For a Protime active/passive dual loop configuration, riders
will not be registered at all until they are moving faster than
about 30 km/h, no matter what software limits have been configured.ConfigurationTrack layout, system transponder ids and speed limits are configured
using a metarace jsonconfig entry with the section 'velotrain' and the
following keys:authkey: (string) reset authorisation string, default: nullgate: (string) transponder ID for start gate, default: nullgatedelay: (string) trigger delay for start gate, default: '0.075'[1]gatesrc: (string) channel start gate reports to eg 'C2', default: nulllaplen: (float) lap length in metres, default: 250.0maxspeed: (float) maximum sector speed in km/d, default: 90.0minspeed: (float) minimum sector speed in km/h, default: 38.0moto: (list) list of transponder IDs attached to motos, default: []trig: (string) transponder ID of sync trigger messages, default: '255'passlevel: (integer) minimum accepted passing level, default 40[2]uaddr: (string) UDP host listen address, default ''uport: (integer) UDP host port, default: 2008bcast: (string) broadcast address for timing LAN,
default: '255.255.255.255'basetopic: (string) base topic for telegraph interface,
default: 'velotrain'sync: (string) channel of synchronisation master unit, default: nullmingate: (float) minimum accepted speed in km/h over the
start gate sector, default: 9.0maxgate: (float) maximum accepted speed in km/h over the
start gate sector, default: 22.5dhi: (list) ['address', port] DHI port for Caprica scoreboard,
default: nulldhiencoding: (string) text encoding for DHI messages, default: 'utf-8'mpseq: (list) ordering of channels on the track,
default: ['C1', 'C9', 'C4', 'C6', 'C3', 'C5', 'C7', 'C8', 'C2'][3]mps: (dict) mapping of channel IDs to measurement point configs,
default: {}[4]Configured measurement points divide the track into a closed loop of
timing sectors, with a set of splits at each measurement point.
Measurement point config entries have the following options, omitted keys
receive a default value:name: (string) visible name of the measurement point,
default: channel IDip: (string) IP address of connected Protime decoder,
default: null[4]offset: (float) distance in metres from finish line to measurement
point, default: nullhalf: (string) channel ID corresponding to a half lap before this unit,
default: nullqtr: (string) channel ID corresponding to quarter lap before this unit,
default: null200: (string) channel ID corresponding to 200 m before this unit,
default: null100: (string) channel ID corresponding to 100 m before this unit,
default: null50: (string) channel ID corresponding to 50 m before this unit,
default: nullNotes:0.075 seconds is LS transponder delay when triggered by
start gate releaseSets the 'level' option in all attached Protime decoder unitsDefault channel ordering matches trackmeet, all configured units
must appear once in this sequence, however the sequence may contain
more channels than are configured.Measurement points require a non-null IP entry to be configured.
For use with foreign timers, set the IP to be an empty string: ''.
Refer to the sample velomon.json for an example settingTelegraph (MQTT) InterfaceThe topics below are relative to the configured basetopic (default:
'velotrain'), from the perspective of the velotrain process. For
all JSON encoded objects, invalid or unavailable properties will be
reported as null.passing (publish)Issue filtered and accepted passing records. Passing records
are JSON encoded objects with the following properties:index : (integer) index of the passing record (reset to 0 each day)date: (string) Date of the passing formatted YYYY-MM-DDtime: (string) Time of day of the passing formatted HH:MM:SS.dcmpid: (integer) Measurement point ID 0 - 9refid: (string) Transponder ID or system passing ID 'gate', 'moto'
or 'marker'env: (list) [temperature, humidity, pressure] where each value
is a float value in units degrees Celsius, %rh, and hPa respectivelymoto: (string) Proximity to moto in seconds if draftedelap: (string) Elapsed time since last start gate or start of run
formatted HH:MM:SS.dclap: (string) Time for the last full lap to the measurement pointhalf: (string) Time for the last half lap to the measurement pointqtr: (string) Time for the last quarter lap to the measurement point200: (string) Time for the last 200m to the measurement point100: (string) Time for the last 100m to the measurement point50: (string) Time for the last 50m to the measurement pointtext: (string) Name of measurement point, or user-supplied text
for markersExample: A moto passing over the 100m split at about 4:18pmTopic:		velotrain/passing
Payload:	{"index": 108, "date": "2022-07-06",
		 "time": "16:18:32.30", "mpid": 5,
		 "refid": "moto", "env": [16.3, 55.0, 1015.0],
		 "moto": "0.00", "elap": "2:10.51",
		 "lap": "22.95", "half": null, "qtr": null,
		 "200": "18.32", "100": "9.07", "50": "4.52",
		 "text": "100m Split"}rawpass (publish)Issue raw, unprocessed passing records as received from decoders. Raw
passing records are JSON encoded objects with the following properties:date: (string) Date of the raw passing formatted YYYY-MM-DDtime: (string) Time of day of the passing formatted HH:MM:SS.dcrcv: (string) Time of day passing was received by host system,
formatted HH:MM:SS.dcmpid: (integer) Measurement point ID 0 - 9refid: (string) Raw transponder IDenv: (list) [temperature, humidity, pressure] where each value
is a float value in units degrees Celsius, %rh, and hPa respectivelyname: (string) Name of measurement pointExample: The raw passing that might have generated the above moto passingTopic:		velotrain/rawpass
Payload:	{"date": "2022-07-06", "env": [16.3, 55.0, 1015.0],
		 "refid": "125328", "mpid": 5,
		 "name": "100m Split", "time": "16:18:32.303",
		 "rcv": "16:18:32.459"}status (publish)Issue system and measurement point status records at the top of each
minute. Status records are JSON encoded objects with the following properties:date: (string) Date of the status record formatted YYYY-MM-DDtime: (string) Time of day status was issued formatted HH:MM:SS.dcoffset: (string) Rough offset of system clock to UTCcount: (integer) Count of passing recordsenv: (list) [temperature, humidity, pressure] where each value is a float value in units degrees Celsius, %rh, and hPa respectivelygate: (string) Time of day of last gate trigger HH:MM:SS.dcunits: (list) List of JSON encoded objects, each containing a measurement
point status:mpid: (integer) Measurement point IDname: (string) Measurement point namenoise: (integer) Interference noise value 0 - 100. Values under
40 indicate normal operation. Larger values indicate interference.offset: (string) Unit clock offset from system time in seconds
formatted as [-]s.dcmExample: Status updateTopic:		velotrain/status
Payload:	{"date": "2022-07-07", "time": "23:04:00.15",
		 "offset": "0.211", "env": [13.1, 62.0, 1013.0],
		 "count": 123, "gate": null,
		 "units": [
		  {"mpid": 1, "name": "Finish",
		   "noise": 20, "offset": "0.000"},
		  {"mpid": 2, "name": "Pursuit A",
		   "noise": 32, "offset": "0.025"},
		  {"mpid": 3, "name": "Pursuit B",
		   "noise": 15, "offset": "0.005"},
		  {"mpid": 4, "name": "200m Start",
		   "noise": 32, "offset": "0.005"},
		  {"mpid": 5, "name": "100m Split",
		   "noise": 14, "offset": "-0.008"},
		  {"mpid": 6, "name": "50m Split",
		   "noise": 27, "offset": "0.064"},
		  {"mpid": 8, "name": "150m Split",
		   "noise": 18, "offset": "-0.020"}
		 ]}request (subscribe)Receive requests for replay of passing records to the nominated serial,
filtered according to the JSON encoded request object:serial (string) optional request serial, appended to replay topicindex (list) optional [first, last] limit response to include
passings from index first to index last.time (list) optional [starttime, endtime] limit response to
passings between starttime and endtime strings formatted HH:MM:SS.dcmpid (list) optional [mpid, ...] include only the nominated
mpids in replayrefid (list) optional [refid, ...] include only nominated
transponder ids in responsemarker (list) optional [marker, ...] include only passings
following the nominated marker stringsExample: Request all passings, publish to default topicTopic:		velotrain/request
Payload:	b''

Reply topic:	velotrain/replay
Payload:	[{"index":0, ...}, {"index":1, ...}, ...]Request a replay of passing 254 to 'xxfd'Topic:		velotrain/request
Payload:	{"serial": "xxfd", "index": 254}

Reply topic:	velotrain/replay/xxfd
Payload:	[{"index": 254, ... }]Replay passings from transponder '123456' and start gate after midday
before index 10000 and that occur following manual markers 'one' and 'two'Topic:		velotrain/request
Payload:	{"refid": ["123456", "gate"],
		 "time": ["12:00:00", null],
		 "index": [null, 10000],
		 "marker": ["one", "two"]}

Reply topic:	velotrain/replay
Payload:	[{"index":12, "text":"one","mpid":0, ...}, ...]replay[/serial] (publish)Issues a JSON encoded list of passing objects, filtered according
to a provided request.marker (subscribe)Insert the provided utf-8 encoded unicode payload as a manual marker
passing. If the supplied payload is empty, 'marker' is used.Example: Insert an emoticon '🤷' (U+1F937) at the current time of dayTopic:		velotrain/marker
Payload:	b'\xf0\x9f\xa4\xb7'reset (subscribe)Start a reset process, authorised by the provided utf-8 encoded
authkey (config option 'authkey'). For a Tag Heuer Protime decoder network,
this process may take up to three minutes to complete. For systems
with foreign timers, this request will just clear passing records.Example: Request reset process using authkey 'qwertyuiop'Topic:		velotrain/reset
Payload:	b'qwertyuiop'timer (subscribe)Receive raw passing data from a foreign timing device. Foreign timing
records are utf-8 encoded unicode strings in the format:INDEX;SOURCE;CHANNEL;REFID;TODWhere the fields are as follows:INDEX: ignoredSOURCE: measurement point id as a metarace timing channel
eg: 'C1', 'C2', 'C3', ... 'C9'CHANNEL: ignoredREFID: transponder ID eg '123456'TOD: time of day of passing as string eg: '1:23.456', 'now', '12:34:56.789'Note: velotrain expects each attached decoder to be triggered from a
reference clock at the top of each minute, and to report a passing with
refid matching config option 'trig' (default: '255').
For systems that maintain synchronisation via a
different mechanism, a fake top of minute trigger can be sent
with the channel of the configured sync source (config key 'sync')
at any time using the trig refid and a TOD of '0'.Example: Insert a passing for transponder '123456' on mpid 3 at the current timePayload: b';C3;;123456;now'Fake a top-of-minute message to mpid 1, using the default trig:Payload: b';C1;;255;0'Insert a passing for transponder '123456' on mpid 4, 10.2345 seconds after 2 pm:Payload: b';C4;;123456;14:00:10.2345'Requirementsmetarace (>= 2.0)ypmeteoInstallation$ pip3 install velotrainTo use as a systemd service, edit the provided unit file
and copy to /etc/systemd/system, then enable with:# systemctl enable velotrain
# systemctl start velotrain