Skip to content

Commit 80cee1b

Browse files
committed
Review logging article
1 parent 9696a3f commit 80cee1b

5 files changed

Lines changed: 40 additions & 162 deletions

File tree

articles/logging.md

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,40 @@
11
# Logging
22

3-
As any other device that is used to record and control an experiment, data streams from `Harp Devices` can also be easily logged in real time.
4-
In the case of `Harp Devices`, the decision as to what to log is somewhat easy. Since all the communication between the peripheral and the host is made via `Harp Messages`, these are the only piece of information one would need to reconstruct the state of the experiment (as seen by the `Harp device` at least) at any given point in time.
5-
Moreover, since `Harp Messages` follow a simple binary protocol, they can be efficiently (both in time and space) logged into disk using a simple flat binary file. The next sections will cover how one can achieve this, and what the current best practices are for logging data streams from `Harp Devices`.
3+
As any other device that is used to record and control an experiment, data streams from Harp devices can also be easily logged in real time. In this case, the decision as to what to log is somewhat easy. Since all the communication between the peripheral and the host is made via a sequence of [`HarpMessage`](xref:Bonsai.Harp.HarpMessage) objects, this is the only piece of information we need to reconstruct the state of the experiment (as seen by the Harp device at least) at any given point in time.
4+
5+
Moreover, since all Harp messages follow a simple binary protocol, they can be efficiently (both in time and space) logged into disk using a simple flat binary file. The next sections will cover how to do this and discuss current recommendations for logging data streams from Harp devices.
66

77
## MessageWriter
88

9-
Since the `Harp` is a binary protocol, any `Harp Message` can be logged by simply saving its raw binary representation. The binary representation (as a `byte[]`) can be accessed via the `MessageBytes` member. Finally, to log the raw binary stream, use a [`MatrixWriter`](xref:Bonsai.Dsp.MatrixWriter) node. Alternatively, the `Bonsai.Harp` package also provides a [`MessageWriter`](xref:Bonsai.Harp.MessageWriter) operator that replicates the previous pattern:
9+
Since [Harp](https://harp-tech.org/About/How-HARP-works/binary_protocol.html) is a binary protocol, any `HarpMessage` can be logged by simply saving its raw binary representation. The binary representation (as a `byte[]`) can be accessed via the [`MessageBytes`](xref:Bonsai.Harp.HarpMessage.MessageBytes) property. This means we could record the entire raw binary stream by feeding the sequence of message bytes to a binary logger.
10+
11+
The `Bonsai.Harp` package provides a dedicated [`MessageWriter`](xref:Bonsai.Harp.MessageWriter) operator that easily encapsulates this functionality:
1012

1113
:::workflow
1214
![LogAllMessages](~/workflows/log-all-messages.bonsai)
1315
:::
1416

15-
Since the logging takes place on top of any `Harp Message` stream, the writers can also be used to: log multiple devices in parallel, log filtered streams (e.g. after applying [`FilterRegister`](xref:Bonsai.Harp.FilterRegister)) or even save host-generated commands (e.g. after a [`CreateMessage`](xref:Bonsai.Harp.CreateMessage)).
17+
Since all logging takes place on top of any `HarpMessage` stream, the writers can also be used to log multiple devices in parallel, log filtered streams (e.g. after applying [`FilterRegister`](xref:Bonsai.Harp.FilterRegister)) or even save host-generated commands (e.g. messages generated by a [`CreateMessage`](xref:Bonsai.Harp.CreateMessage) operator).
1618

1719
## GroupByRegister
1820

19-
While logging all `Harp Messages` to a single binary is certainly possible, it is not always the most convenient way to log data. For instance, if one is interested in logging only a subset of the `Harp Messages` (e.g. only the `ADC` messages), then the previous approach would require a post-processing step to filter out the messages of interest. Moreoever, each address has potentially different data formats (e.g. `U8` vs `U16`) or length. As a result, while parsing a binary file, the user will have to read and parse a single message at a time. Alternatively, one can use a `Demux` pattern to log the `Harp Messages`, from different addresses, into separate files. This way, one can ensure that all messages on a single file have the same format and length and can thus be read and parsed in one pass.
21+
While logging all Harp messages to a single binary is very easy, it is not always the most convenient way to log data. For instance, if one is interested in logging only a subset of messages (e.g. only the `ADC` messages), then the previous approach would require a post-processing step to filter out the messages of interest.
22+
23+
Furthermore, each address has potentially different data formats (e.g. `U8` vs `U16`) or even different lengths if array registers are involved. This can make it very tedious to parse and analyze a binary file offline, since we will have to examine the header of each and every message in the file to determine how to extract its contents.
24+
25+
This analysis could be entirely eliminated if we knew that all messages in the binary file had the same format. For any Harp device, the payload stored in a specific register will have a fixed type and length. This means that to ensure our simplifying assumption it is enough to save each message from a specific register into a different file.
26+
27+
The [`GroupByRegister`](xref:Bonsai.Harp.GroupByRegister) operator is designed to automatically split the single Harp message stream into independent sub-streams for each device register address. There are many applications of this advanced operator, but the most common one is to demultiplex register messages before applying the `MessageWriter` operator. If `MessageWriter` receives a grouped message sequence, it will automatically generate one independent file for each register.
2028

21-
A possible implementation of this pattern is shown below:
29+
If we do this, we can ensure that all messages on each single file will have the same format and length and can thus be read and parsed in a single bulk operation. A simple implementation of this pattern is shown below:
2230

2331
:::workflow
2432
![LogDemux](~/workflows/log-demux.bonsai)
2533
:::
2634

2735
The single-register log files can then be loaded using the following Python routine:
2836

29-
```Python
37+
```python
3038
import numpy as np
3139
import pandas as pd
3240

workflows/log-all-messages.bonsai

Lines changed: 8 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
<?xml version="1.0" encoding="utf-8"?>
2-
<WorkflowBuilder Version="2.7.3"
2+
<WorkflowBuilder Version="2.8.0"
33
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
44
xmlns:rx="clr-namespace:Bonsai.Reactive;assembly=Bonsai.Core"
55
xmlns:harp="clr-namespace:Bonsai.Harp;assembly=Bonsai.Harp"
6-
xmlns:dsp="clr-namespace:Bonsai.Dsp;assembly=Bonsai.Dsp"
76
xmlns="https://bonsai-rx.org/2018/workflow">
87
<Workflow>
98
<Nodes>
@@ -21,39 +20,23 @@
2120
<harp:PortName>COMx</harp:PortName>
2221
</Combinator>
2322
</Expression>
24-
<Expression xsi:type="rx:PublishSubject">
25-
<Name>DeviceEvents</Name>
26-
</Expression>
27-
<Expression xsi:type="SubscribeSubject">
28-
<Name>DeviceEvents</Name>
29-
</Expression>
30-
<Expression xsi:type="MemberSelector">
31-
<Selector>MessageBytes</Selector>
32-
</Expression>
33-
<Expression xsi:type="Combinator">
34-
<Combinator xsi:type="dsp:MatrixWriter">
35-
<dsp:Path>file.bin</dsp:Path>
36-
<dsp:Suffix>None</dsp:Suffix>
37-
<dsp:Overwrite>false</dsp:Overwrite>
38-
<dsp:Layout>ColumnMajor</dsp:Layout>
39-
</Combinator>
40-
</Expression>
41-
<Expression xsi:type="SubscribeSubject">
42-
<Name>DeviceEvents</Name>
43-
</Expression>
4423
<Expression xsi:type="Combinator">
4524
<Combinator xsi:type="harp:MessageWriter">
4625
<harp:Suffix>None</harp:Suffix>
26+
<harp:Buffered>true</harp:Buffered>
4727
<harp:Overwrite>false</harp:Overwrite>
28+
<harp:FilterType>Include</harp:FilterType>
29+
<harp:MessageType xsi:nil="true" />
4830
</Combinator>
4931
</Expression>
32+
<Expression xsi:type="rx:PublishSubject">
33+
<Name>DeviceEvents</Name>
34+
</Expression>
5035
</Nodes>
5136
<Edges>
5237
<Edge From="0" To="1" Label="Source1" />
5338
<Edge From="1" To="2" Label="Source1" />
54-
<Edge From="3" To="4" Label="Source1" />
55-
<Edge From="4" To="5" Label="Source1" />
56-
<Edge From="6" To="7" Label="Source1" />
39+
<Edge From="2" To="3" Label="Source1" />
5740
</Edges>
5841
</Workflow>
5942
</WorkflowBuilder>

0 commit comments

Comments
 (0)