88import pandas as pd
99from pandas ._typing import Axes
1010
11- from harp .typing import BufferLike
11+ from harp .typing import _BufferLike , _FileLike
1212
1313REFERENCE_EPOCH = datetime (1904 , 1 , 1 )
1414"""The reference epoch for UTC harp time."""
@@ -41,30 +41,30 @@ class MessageType(IntEnum):
4141
4242
4343def read (
44- file : Union [str , bytes , PathLike [ Any ], BinaryIO ],
44+ file_or_buf : Union [_FileLike , _BufferLike ],
4545 address : Optional [int ] = None ,
4646 dtype : Optional [np .dtype ] = None ,
4747 length : Optional [int ] = None ,
4848 columns : Optional [Axes ] = None ,
4949 epoch : Optional [datetime ] = None ,
5050 keep_type : bool = False ,
5151):
52- """Read single-register Harp data from the specified file.
52+ """Read single-register Harp data from the specified file or buffer .
5353
5454 Parameters
5555 ----------
56- file
57- Open file object or filename containing binary data from
56+ file_or_buf
57+ File path, open file object, or buffer containing binary data from
5858 a single device register.
5959 address
6060 Expected register address. If specified, the address of
61- the first message in the file is used for validation.
61+ the first message is used for validation.
6262 dtype
6363 Expected data type of the register payload. If specified, the
64- payload type of the first message in the file is used for validation.
64+ payload type of the first message is used for validation.
6565 length
6666 Expected number of elements in register payload. If specified, the
67- payload length of the first message in the file is used for validation.
67+ payload length of the first message is used for validation.
6868 columns
6969 The optional column labels to use for the data values.
7070 epoch
@@ -77,60 +77,13 @@ def read(
7777 -------
7878 A pandas data frame containing message data, sorted by time.
7979 """
80- data = np .fromfile (file , dtype = np .uint8 )
81- return _fromraw (data , address , dtype , length , columns , epoch , keep_type )
82-
83-
84- def parse (
85- buffer : BufferLike ,
86- address : Optional [int ] = None ,
87- dtype : Optional [np .dtype ] = None ,
88- length : Optional [int ] = None ,
89- columns : Optional [Axes ] = None ,
90- epoch : Optional [datetime ] = None ,
91- keep_type : bool = False ,
92- ):
93- """Parse single-register Harp data from the specified buffer.
94-
95- Parameters
96- ----------
97- buffer
98- An object that exposes a buffer interface containing binary data from
99- a single device register.
100- address
101- Expected register address. If specified, the address of
102- the first message in the buffer is used for validation.
103- dtype
104- Expected data type of the register payload. If specified, the
105- payload type of the first message in the buffer is used for validation.
106- length
107- Expected number of elements in register payload. If specified, the
108- payload length of the first message in the buffer is used for validation.
109- columns
110- The optional column labels to use for the data values.
111- epoch
112- Reference datetime at which time zero begins. If specified,
113- the result data frame will have a datetime index.
114- keep_type
115- Specifies whether to include a column with the message type.
116-
117- Returns
118- -------
119- A pandas data frame containing message data, sorted by time.
120- """
121- data = np .frombuffer (buffer , dtype = np .uint8 )
122- return _fromraw (data , address , dtype , length , columns , epoch , keep_type )
123-
80+ if isinstance (file_or_buf , (str , PathLike , BinaryIO )) or hasattr (file_or_buf , "readinto" ):
81+ # TODO: in the below we ignore the type as otherwise
82+ # we have no way to runtime check _IOProtocol
83+ data = np .fromfile (file_or_buf , dtype = np .uint8 ) # type: ignore
84+ else :
85+ data = np .frombuffer (file_or_buf , dtype = np .uint8 )
12486
125- def _fromraw (
126- data : npt .NDArray [np .uint8 ],
127- address : Optional [int ] = None ,
128- dtype : Optional [np .dtype ] = None ,
129- length : Optional [int ] = None ,
130- columns : Optional [Axes ] = None ,
131- epoch : Optional [datetime ] = None ,
132- keep_type : bool = False ,
133- ):
13487 if len (data ) == 0 :
13588 return pd .DataFrame (
13689 columns = columns ,
@@ -185,11 +138,12 @@ def _fromraw(
185138 return result
186139
187140
188- def write (
189- file : Union [str , bytes , PathLike [Any ], BinaryIO ],
141+ def to_file (
190142 data : pd .DataFrame ,
143+ file : _FileLike ,
191144 address : int ,
192145 dtype : Optional [np .dtype ] = None ,
146+ length : Optional [int ] = None ,
193147 port : Optional [int ] = None ,
194148 epoch : Optional [datetime ] = None ,
195149 message_type : Optional [MessageType ] = None ,
@@ -198,16 +152,19 @@ def write(
198152
199153 Parameters
200154 ----------
201- file
202- Open file object or filename where to store binary data from
203- a single device register.
204155 data
205156 Pandas data frame containing message payload.
157+ file
158+ File path, or open file object in which to store binary data from
159+ a single device register.
206160 address
207161 Register address used to identify all formatted Harp messages.
208162 dtype
209163 Data type of the register payload. If specified, all data will
210164 be converted before formatting the binary payload.
165+ length
166+ Expected number of elements in register payload. If specified, the
167+ number of columns in the input data frame is validated.
211168 port
212169 Optional port value used for all formatted Harp messages.
213170 epoch
@@ -217,19 +174,20 @@ def write(
217174 Optional message type used for all formatted Harp messages.
218175 If not specified, data must contain a MessageType column.
219176 """
220- buffer = format (data , address , dtype , port , epoch , message_type )
177+ buffer = to_buffer (data , address , dtype , port , length , epoch , message_type )
221178 buffer .tofile (file )
222179
223180
224- def format (
181+ def to_buffer (
225182 data : pd .DataFrame ,
226183 address : int ,
227184 dtype : Optional [np .dtype ] = None ,
185+ length : Optional [int ] = None ,
228186 port : Optional [int ] = None ,
229187 epoch : Optional [datetime ] = None ,
230188 message_type : Optional [MessageType ] = None ,
231189) -> npt .NDArray [np .uint8 ]:
232- """Format single-register Harp data as a flat binary buffer.
190+ """Convert single-register Harp data to a flat binary buffer.
233191
234192 Parameters
235193 ----------
@@ -240,6 +198,9 @@ def format(
240198 dtype
241199 Data type of the register payload. If specified, all data will
242200 be converted before formatting the binary payload.
201+ length
202+ Expected number of elements in register payload. If specified, the
203+ number of columns in the input data frame is validated.
243204 port
244205 Optional port value used for all formatted Harp messages.
245206 epoch
@@ -254,12 +215,13 @@ def format(
254215 An array object containing message data formatted according
255216 to the Harp binary protocol.
256217 """
257- if len (data ) == 0 :
218+ nrows = len (data )
219+ if nrows == 0 :
258220 return np .empty (0 , dtype = np .uint8 )
259221
260- if " MessageType" in data .columns :
261- msgtype = data [" MessageType" ].cat .codes
262- payload = data [data .columns .drop (" MessageType" )].values
222+ if MessageType . __name__ in data .columns :
223+ msgtype = data [MessageType . __name__ ].cat .codes
224+ payload = data [data .columns .drop (MessageType . __name__ )].values
263225 elif message_type is not None :
264226 msgtype = message_type
265227 payload = data .values
@@ -278,17 +240,20 @@ def format(
278240 if dtype is not None :
279241 payload = payload .astype (dtype , copy = False )
280242
243+ ncols = payload .shape [1 ]
244+ if length is not None and ncols != length :
245+ raise ValueError (f"expected payload length { length } but got { ncols } " )
246+
281247 if port is None :
282248 port = 255
283249
284250 payloadtype = _payloadtypefromdtype [payload .dtype ]
285- payloadlength = payload . shape [ 1 ] * payload .dtype .itemsize
251+ payloadlength = ncols * payload .dtype .itemsize
286252 stride = payloadlength + 6
287253 if is_timestamped :
288254 payloadtype |= _PAYLOAD_TIMESTAMP_MASK
289255 stride += 6
290256
291- nrows = len (data )
292257 buffer = np .empty ((nrows , stride ), dtype = np .uint8 )
293258 buffer [:, 0 ] = msgtype
294259 buffer [:, 1 :5 ] = [stride - 2 , address , port , payloadtype ]
0 commit comments