Skip to content

Commit e81a9ba

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 e81a9ba

File tree

9 files changed

+290
-2
lines changed

9 files changed

+290
-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
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: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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+
print(rb.read())
34+
35+
try:
36+
# size must be int.
37+
micropython.ringbuffer(None)
38+
except TypeError as ex:
39+
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy