8000 py/ringbuf: Add micropython.ringbuffer() interface for general use. · micropython/micropython@45f1b96 · GitHub
[go: up one dir, main page]

Skip to content

Commit 45f1b96

Browse files
committed
py/ringbuf: Add micropython.ringbuffer() interface for general use.
Signed-off-by: Andrew Leech <andrew.leech@planetinnovation.com.au>
1 parent d1685a3 commit 45f1b96

File tree

9 files changed

+289
-2
lines changed

9 files changed

+289
-2
lines changed

docs/library/micropython.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,3 +147,15 @@ Functions
147147

148148
There is a finite queue to hold the scheduled functions and `schedule()`
149149
will raise a `RuntimeError` if the queue is full.
150+
151+
.. function:: ringbuffer(size)
152+
.. function:: ringbuffer(buffer)
153+
:noindex:
154+
155+
Provides a fixed-size ringbuffer with stream interface. Can be used similar to
156+
`io.BytesIO` however has separate read/write positions.
157+
158+
Can be created with integer size provided and a suitable buffer will be created,
159+
after which no further allocations will take place during use.
160+
Alternatively a `bytearray`, `memoryview` or similar object can be provided at
161+
init for in-place use.

py/modmicropython.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include "py/runtime.h"
3232
#include "py/gc.h"
3333
#include "py/mphal.h"
34+
#include "py/ringbuf.h"
3435

3536
#if MICROPY_PY_MICROPYTHON
3637

@@ -203,6 +204,9 @@ static const mp_rom_map_elem_t mp_module_micropython_globals_table[] = {
203204
#if MICROPY_ENABLE_SCHEDULER
204205
{ MP_ROM_QSTR(MP_QSTR_schedule), MP_ROM_PTR(&mp_micropython_schedule_obj) },
205206
#endif
207+
#if MICROPY_PY_MICROPYTHON_RINGBUFFER
208+
{ MP_ROM_QSTR(MP_QSTR_ringbuffer), MP_ROM_PTR(&mp_type_micropython_ringbuffer) },
209+
#endif
206210
};
207211

208212
static MP_DEFINE_CONST_DICT(mp_module_micropython_globals, mp_module_micropython_globals_table);

py/mpconfig.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -981,6 +981,11 @@ typedef double mp_float_t;
981981
#define MICROPY_ENABLE_SCHEDULER (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES)
982982
#endif
983983

984+
// Support for micropython.ringbuffer()
985+
#ifndef MICROPY_PY_MICROPYTHON_RINGBUFFER
986+
#define MICROPY_PY_MICROPYTHON_RINGBUFFER (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES)
987+
#endif
988+
984989
// Whether the scheduler supports scheduling static nodes with C callbacks
985990
#ifndef MICROPY_SCHEDULER_STATIC_NODES
986991
#define MICROPY_SCHEDULER_STATIC_NODES (0)

py/ringbuf.c

Lines changed: 172 additions & 0 deletions
< 1241 /tr>
Original file line numberDiff line numberDiff line change
@@ -118,3 +118,175 @@ int ringbuf_put_bytes(ringbuf_t *r, const uint8_t *data, size_t data_len) {
118118
r->iput = iput_a;
119119
return 0;
120120
}
121+
122+
#if MICROPY_PY_MICROPYTHON_RINGBUFFER
123+
124+
#include "py/runtime.h"
125+
#include "py/stream.h"
126+
#include "py/mphal.h"
127+
128+
typedef struct _micropython_ringbuffer_obj_t {
129+
mp_obj_base_t base;
130+
ringbuf_t ringbuffer;
131+
mp_int_t timeout; // timeout waiting for first char (in ms)
132+
} micropython_ringbuffer_obj_t;
133+
134+
static mp_obj_t micropython_ringbuffer_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {
135+
mp_arg_check_num(n_args, n_kw, 1, 2, false);
136+
mp_int_t buff_size = -1;
137+
mp_buffer_info_t bufinfo = {NULL, 0, 0};
138+
139+
if (!mp_get_buffer(args[0], &bufinfo, MP_BUFFER_RW)) {
140+
buff_size = mp_obj_get_int(args[0]);
141+
}
142+
micropython_ringbuffer_obj_t *self = mp_obj_malloc(micropython_ringbuffer_obj_t, type);
143+
if (bufinfo.buf != NULL) {
144+
// buffer passed in, use it directly for ringbuffer
145+
self->ringbuffer.buf = bufinfo.buf;
146+
self->ringbuffer.size = bufinfo.len;
147+
self->ringbuffer.iget = self->ringbuffer.iput = 0;
148+
} else {
149+
// Allocation buffer, add one extra to buff_size as ringbuf consumes one byte for tracking.
150+
ringbuf_alloc(&(self->ringbuffer), buff_size + 1);
151+
}
152+
153+
if (n_args > 1) {
154+
self->timeout = mp_obj_get_int(args[1]);
155+
} else {
156+
self->timeout = -1;
157+
}
158+
return MP_OBJ_FROM_PTR(self);
159+
}
160+
161+
static mp_obj_t micropython_ringbuffer_settimeout(mp_obj_t self_in, mp_obj_t timeout_in) {
162+
micropython_ringbuffer_obj_t *self = MP_OBJ_TO_PTR(self_in);
163+
self->timeout = mp_obj_get_int(timeout_in);
164+
return mp_const_none;
165+
}
166+
static MP_DEFINE_CONST_FUN_OBJ_2(micropython_ringbuffer_settimeout_obj, micropython_ringbuffer_settimeout);
167+
168+
169+
static mp_uint_t micropython_ringbuffer_read(mp_obj_t self_in, void *buf_in, mp_uint_t size, int *errcode) {
170+
micropython_ringbuffer_obj_t *self = MP_OBJ_TO_PTR(self_in);
171+
mp_uint_t start = mp_hal_ticks_ms();
172+
173+
size = MIN(size, ((mp_uint_t)self->ringbuffer.size - 1)); // limit size to ringbuffer length
174+
175+
while (ringbuf_get_bytes(&self->ringbuffer, buf_in, size) == -1) {
176+
if (self->timeout > -1 && (mp_hal_ticks_ms() - start > (mp_uint_t)self->timeout)) {
177+
// timed out
178+
if ((size = MIN(size, ringbuf_avail(&self->ringbuffer))) > 0) {
179+
// return available data
180+
ringbuf_get_bytes(&self->ringbuffer, buf_in, size);
181+
*errcode = 0;
182+
return size;
183+
}
184+
// no data available
185+
*errcode = MP_EAGAIN;
186+
return MP_STREAM_ERROR;
187+
}
188+
mp_event_handle_nowait();
189+
}
190+
*errcode = 0;
191+
return size;
192+
}
193+
194+
static mp_uint_t micropython_ringbuffer_write(mp_obj_t self_in, const void *buf_in, mp_uint_t size, int *errcode) {
195+
micropython_ringbuffer_obj_t *self = MP_OBJ_TO_PTR(self_in);
196+
mp_uint_t start = mp_hal_ticks_ms();
197+
198+
size = MIN(size, ((mp_uint_t)self->ringbuffer.size - 1)); // limit size to ringbuffer length
199+
200+
while (ringbuf_put_bytes(&self->ringbuffer, buf_in, size) == -1) {
201+
if (self->timeout > -1 && (mp_hal_ticks_ms() - start > (mp_uint_t)self->timeout)) {
202+
// timed out
203+
if ((size = MIN(size, ringbuf_free(&self->ringbuffer))) > 0) {
204+
// write whatever can fit
205+
ringbuf_put_bytes(&self->ringbuffer, buf_in, size);
206+
*errcode = 0;
207+
return size;
208+
}
209+
// no space available
210+
*errcode = MP_EAGAIN;
211+
return MP_STREAM_ERROR;
212+
}
213+
mp_event_handle_nowait();
214+
}
215+
*errcode = 0;
216+
return size;
217+
218+
}
219+
220+
static mp_uint_t micropython_ringbuffer_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_t arg, int *errcode) {
221+
micropython_ringbuffer_obj_t *self = MP_OBJ_TO_PTR(self_in);
222+
mp_uint_t ret;
223+
if (request == MP_STREAM_POLL) {
224+
ret = 0;
225+
if ((arg & MP_STREAM_POLL_RD) && ringbuf_avail(&self->ringbuffer) > 0) {
226+
ret |= MP_STREAM_POLL_RD;
227+
}
228+
if ((arg & MP_STREAM_POLL_WR) && ringbuf_free(&self->ringbuffer) > 0) {
229+
ret |= MP_STREAM_POLL_WR;
230+
}
231+
} else if (request == MP_STREAM_FLUSH) {
232+
// Should we wait here until empty / timeout?
233+
ret = 0;
234+
} else if (request == MP_STREAM_CLOSE) {
235+
// We don't want to reset head/tail pointers as there might
236+
// still be someone using it, eg. if ringbuffer is used instead of
237+
// a socket, a "writer" might call close before the "reader" is
238+
// finished.
239+
// Should we flush here though?
240+
ret = 0;
241+
} else {
242+
*errcode = MP_EINVAL;
243+
ret = MP_STREAM_ERROR;
244+
}
245+
return ret;
246+
}
247+
248+
static mp_obj_t micropython_ringbuffer_any(mp_obj_t self_in) {
249+
micropython_ringbuffer_obj_t *self = MP_OBJ_TO_PTR(self_in);
250+
return MP_OBJ_NEW_SMALL_INT(ringbuf_avail(&self->ringbuffer));
251+
}
252+
static MP_DEFINE_CONST_FUN_OBJ_1(micropython_ringbuffer_any_obj, micropython_ringbuffer_any);
253+
254+
static mp_obj_t micropython_ringbuffer_reset(mp_obj_t self_in) {
255+
micropython_ringbuffer_obj_t *self = MP_OBJ_TO_PTR(self_in);
256+
self->ringbuffer.iget = self->ringbuffer.iput = 0;
257+
return mp_const_none;
258+
}
259+
static MP_DEFINE_CONST_FUN_OBJ_1(micropython_ringbuffer_reset_obj, micropython_ringbuffer_reset);
260+
261+
262+
static const mp_rom_map_elem_t micropython_ringbuffer_locals_dict_table[] = {
263+
{ MP_ROM_QSTR(MP_QSTR_any), MP_ROM_PTR(&micropython_ringbuffer_any_obj) },
264+
{ MP_ROM_QSTR(MP_QSTR_settimeout), MP_ROM_PTR(&micropython_ringbuffer_settimeout_obj) },
265+
{ MP_ROM_QSTR(MP_QSTR_reset), MP_ROM_PTR(&micropython_ringbuffer_reset_obj) },
266+
{ MP_ROM_QSTR(MP_QSTR_flush), MP_ROM_PTR(&mp_stream_flush_obj) },
267+
{ MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_stream_read_obj) },
268+
{ MP_ROM_QSTR(MP_QSTR_readline), MP_ROM_PTR(&mp_stream_unbuffered_readline_obj) },
269+
{ MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) },
270+
{ MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) },
271+
{ MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&mp_stream_close_obj) },
272+
273+
};
274+
static MP_DEFINE_CONST_DICT(micropython_ringbuffer_locals_dict, micropython_ringbuffer_locals_dict_table);
275+
276+
static const mp_stream_p_t ringbuffer_stream_p = {
277+
.read = micropython_ringbuffer_read,
278+
.write = micropython_ringbuffer_write,
279+
.ioctl = micropython_ringbuffer_ioctl,
280+
.is_text = false,
281+
};
282+
283+
MP_DEFINE_CONST_OBJ_TYPE(
284+
mp_type_micropython_ringbuffer,
285+
MP_QSTR_ringbuffer,
286+
MP_TYPE_FLAG_NONE,
287+
make_new, micropython_ringbuffer_make_new,
288+
protocol, &ringbuffer_stream_p,
289+
locals_dict, &micropython_ringbuffer_locals_dict
290+
);
291+
292+
#endif

py/ringbuf.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,10 @@
2828

2929
#include <stddef.h>
3030
#include <stdint.h>
31+
#include "py/mpconfig.h"
3132

32-
#ifdef _MSC_VER
33-
#include "py/mpconfig.h" // For inline.
33+
#if MICROPY_PY_MICROPYTHON_RINGBUFFER
34+
#include "py/obj.h"
3435
#endif
3536

3637
typedef struct _ringbuf_t {
@@ -99,4 +100,8 @@ int ringbuf_put16(ringbuf_t *r, uint16_t v);
99100
int ringbuf_get_bytes(ringbuf_t *r, uint8_t *data, size_t data_len);
100101
int ringbuf_put_bytes(ringbuf_t *r, const uint8_t *data, size_t data_len);
101102

103+
#if MICROPY_PY_MICROPYTHON_RINGBUFFER
104+
extern const mp_obj_type_t mp_type_micropython_ringbuffer;
105+
#endif
106+
102107
#endif // MICROPY_INCLUDED_PY_RINGBUF_H

tests/micropython/ringbuffer.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# check that micropython.ringbuffer works correctly.
2+
3+
import micropython
4+
5+
try:
6+
micropython.ringbuffer
7+
except AttributeError:
8+
print("SKIP")
9+
raise SystemExit
10+
11+
rb = micropython.ringbuffer(16, 1)
12+
print(rb)
13+
14+
print(rb.any())
15+
16+
rb.write(b"\x00")
17+
print(rb.any())
18+
19+
rb.write(b"\x00")
20+
print(rb.any())
21+
22+
print(rb.read(2))
23+
print(rb.any())
24+
25+
26+
rb.write(b"\x00\x01")
27+
print(rb.read())
28+
29+
print(rb.read(1))
30+
31+
print(rb.write(b"\x00\x01" * 10))
32+
print(rb.read())
33+
34+
try:
35+
# size must be int.
36+
micropython.ringbuffer(None)
37+
except TypeError as ex:
38+
print(ex)

tests/micropython/ringbuffer.py.exp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<ringbuffer>
2+
0
3+
1
4+
2
5+
b'\x00\x00'
6+
0
7+
b'\x00\x01'
8+
None
9+
16
10+
b'\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01'
11+
can't convert NoneType to int

tests/micropython/ringbuffer_async.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# check that micropython.ringbuffer works correctly with asyncio.Stream.
2+
3+
import micropython
4+
5+
try:
6+
import asyncio
7+
8+
micropython.ringbuffer
9+
except AttributeError:
10+
print("SKIP")
11+
raise SystemExit
12+
13+
rb = micropython.ringbuffer(16, 0)
14+
rba = asyncio.StreamWriter(rb)
15+
16+
data = b"ABC123" * 20
17+
print("w", len(data))
18+
19+
20+
async def data_writer():
21+
global data
22+
rba.write(data)
23+
await rba.drain()
24+
25+
26+
async def main():
27+
task = asyncio.create_task(data_writer())
28+
await asyncio.sleep_ms(10)
29+
# buff = bytearray(len(data))
30+
read = await rba.readexactly(len(data))
31+
print(read)
32+
print("r", len(read))
33+
print(read == data)
34+
35+
36+
asyncio.run(main())
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
w 120
2+
b'ABC123ABC123ABC123ABC123ABC123ABC123ABC123ABC123ABC123ABC123ABC123ABC123ABC123ABC123ABC123ABC123ABC123ABC123ABC123ABC123'
3+
r 120
4+
True

0 commit comments

Comments
 (0)
0