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

Skip to content

Commit 60d6a6a

Browse files
committed
py/ringbuf: Add micropython.ringbuffer() interface for general use.
This work was funded by Planet Innovation.
1 parent bdbc444 commit 60d6a6a

File tree

7 files changed

+243
-4
lines changed

7 files changed

+243
-4
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 buffer with stream interface. Can be used similar to
156+
`io.BytesIO` however once created no futher memory allocations will ever
157+
take place.
158+
159+
Can be created with integer size provided and a suitable buffer will be created.
160+
Alternatively a `bytearray`, `memoryview` or similar object can be provided for
161+
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
// Various builtins specific to MicroPython runtime,
3637
// living in micropython module
@@ -201,6 +202,9 @@ STATIC const mp_rom_map_elem_t mp_module_micropython_globals_table[] = {
201202
#if MICROPY_ENABLE_SCHEDULER
202203
{ MP_ROM_QSTR(MP_QSTR_schedule), MP_ROM_PTR(&mp_micropython_schedule_obj) },
203204
#endif
205+
#if MICROPY_PY_MICROPYTHON_RINGBUFFER
206+
{ MP_ROM_QSTR(MP_QSTR_ringbuffer), MP_ROM_PTR(&mp_type_micropython_ringbuffer) },
207+
#endif
204208
};
205209

206210
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
@@ -904,6 +904,11 @@ typedef double mp_float_t;
904904
#define MICROPY_ENABLE_SCHEDULER (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES)
905905
#endif
906906

907+
// Support for micropython.ringbuffer()
908+
#ifndef MICROPY_PY_MICROPYTHON_RINGBUFFER
909+
#define MICROPY_PY_MICROPYTHON_RINGBUFFER (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES)
910+
#endif
911+
907912
// Whether the scheduler supports scheduling static nodes with C callbacks
908913
#ifndef MICROPY_SCHEDULER_STATIC_NODES
909914
#define MICROPY_SCHEDULER_STATIC_NODES (0)

py/ringbuf.c

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,170 @@ int ringbuf_put16(ringbuf_t *r, uint16_t v) {
7171
r->iput = iput_b;
7272
return 0;
7373
}
74+
75+
#if MICROPY_PY_MICROPYTHON_RINGBUFFER
76+
77+
#include "py/runtime.h"
78+
#include "py/stream.h"
79+
#include "py/mphal.h"
80+
81+
typedef struct _micropython_ringbuffer_obj_t {
82+
mp_obj_base_t base;
83+
ringbuf_t ringbuffer;
84+
uint16_t timeout; // timeout waiting for first char (in ms)
85+
} micropython_ringbuffer_obj_t;
86+
87+
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) {
88+
mp_arg_check_num(n_args, n_kw, 1, 2, false);
89+
mp_int_t buff_size = -1;
90+
mp_buffer_info_t bufinfo = {NULL, 0, 0};
91+
92+
if (!mp_get_buffer(args[0], &bufinfo, MP_BUFFER_RW)) {
93+
buff_size = mp_obj_get_int(args[0]);
94+
}
95+
micropython_ringbuffer_obj_t *self = mp_obj_malloc(micropython_ringbuffer_obj_t, type);
96+
if (bufinfo.buf != NULL) {
97+
// buffer passed in, use it directly for ringbuffer
98+
self->ringbuffer.buf = bufinfo.buf;
99+
self->ringbuffer.size = bufinfo.len;
100+
self->ringbuffer.iget = self->ringbuffer.iput = 0;
101+
} else {
102+
// Allocation buffer, sdd one extra to buff_size as ringbuf consumes one byte for tracking.
103+
ringbuf_alloc(&(self->ringbuffer), buff_size + 1);
104+
}
105+
106+
if (n_args > 1) {
107+
self->timeout = mp_obj_get_int(args[1]);
108+
}
109+
return MP_OBJ_FROM_PTR(self);
110+
}
111+
112+
STATIC mp_obj_t micropython_ringbuffer_settimeout(mp_obj_t self_in, mp_obj_t timeout_in) {
113+
micropython_ringbuffer_obj_t *self = MP_OBJ_TO_PTR(self_in);
114+
self->timeout = mp_obj_get_int(timeout_in);
115+
return mp_const_none;
116+
}
117+
STATIC MP_DEFINE_CONST_FUN_OBJ_2(micropython_ringbuffer_settimeout_obj, micropython_ringbuffer_settimeout);
118+
119+
120+
STATIC mp_uint_t micropython_ringbuffer_read(mp_obj_t self_in, void *buf_in, mp_uint_t size, int *errcode) {
121+
micropython_ringbuffer_obj_t *self = MP_OBJ_TO_PTR(self_in);
122+
uint32_t t = mp_hal_ticks_ms() + self->timeout;
123+
uint8_t *dest = buf_in;
124+
125+
for (size_t i = 0; i < size; i++) {
126+
// Wait for the first/next character.
127+
while (ringbuf_avail(&self->ringbuffer) == 0) {
128+
if (mp_hal_ticks_ms() > t) { // timed out
129+
if (i <= 0) {
130+
*errcode = MP_EAGAIN;
131+
return MP_STREAM_ERROR;
132+
} else {
133+
return i;
134+
}
135+
}
136+
MICROPY_EVENT_POLL_HOOK
137+
}
138+
*dest++ = ringbuf_get(&(self->ringbuffer));
139+
t = mp_hal_ticks_ms() + self->timeout;
140+
}
141+
return size;
142+
}
143+
144+
STATIC mp_uint_t micropython_ringbuffer_write(mp_obj_t self_in, const void *buf_in, mp_uint_t size, int *errcode) {
145+
micropython_ringbuffer_obj_t *self = MP_OBJ_TO_PTR(self_in);
146+
uint32_t t = mp_hal_ticks_ms() + self->timeout;
147+
const uint8_t *src = buf_in;
148+
size_t i = 0;
149+
150+
// Put as many bytes as possible into the transmit buffer.
151+
while (i < size && ringbuf_free(&(self->ringbuffer)) > 0) {
152+
ringbuf_put(&(self->ringbuffer), *src++);
153+
++i;
154+
}
155+
// If ringbuf full, block until drained elsewhere (eg. irq) or timeout.
156+
while (i < size) {
157+
while (ringbuf_free(&(self->ringbuffer)) == 0) {
158+
if (mp_hal_ticks_ms() > t) { // timed out
159+
if (i <= 0) {
160+
*errcode = MP_EAGAIN;
161+
return MP_STREAM_ERROR;
162+
} else {
163+
return i;
164+
}
165+
}
166+
MICROPY_EVENT_POLL_HOOK
167+
}
168+
ringbuf_put(&(self->ringbuffer), *src++);
169+
++i;
170+
t = mp_hal_ticks_ms() + self->timeout;
171+
}
172+
// Just in case the fifo was drained during refill of the ringbuf.
173+
return size;
174+
}
175+
176+
STATIC mp_uint_t micropython_ringbuffer_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_t arg, int *errcode) {
177+
micropython_ringbuffer_obj_t *self = MP_OBJ_TO_PTR(self_in);
178+
mp_uint_t ret;
179+
if (request == MP_STREAM_POLL) {
180+
ret = 0;
181+
if ((arg & MP_STREAM_POLL_RD) && ringbuf_avail(&self->ringbuffer) > 0) {
182+
ret |= MP_STREAM_POLL_RD;
183+
}
184+
if ((arg & MP_STREAM_POLL_WR) && ringbuf_free(&self->ringbuffer) > 0) {
185+
ret |= MP_STREAM_POLL_WR;
186+
}
187+
} else if (request == MP_STREAM_FLUSH) {
188+
// Should we wait here until empty / timeout?
189+
ret = 0;
190+
} else if (request == MP_STREAM_CLOSE) {
191+
// We don't want to reset head/tail pointers as there might
192+
// still be someone using it, eg. if ringbuffer is used instead of
193+
// a socket, a "writer" might call close before the "reader" is
194+
// finished.
195+
// Should we flush here though?
196+
ret = 0;
197+
} else {
198+
*errcode = MP_EINVAL;
199+
ret = MP_STREAM_ERROR;
200+
}
201+
return ret;
202+
}
203+
204+
STATIC mp_obj_t micropython_ringbuffer_any(mp_obj_t self_in) {
205+
micropython_ringbuffer_obj_t *self = MP_OBJ_TO_PTR(self_in);
206+
return MP_OBJ_NEW_SMALL_INT(ringbuf_avail(&self->ringbuffer));
207+
}
208+
STATIC MP_DEFINE_CONST_FUN_OBJ_1(micropython_ringbuffer_any_obj, micropython_ringbuffer_any);
209+
210+
211+
STATIC const mp_rom_map_elem_t micropython_ringbuffer_locals_dict_table[] = {
212+
{ MP_ROM_QSTR(MP_QSTR_any), MP_ROM_PTR(&micropython_ringbuffer_any_obj) },
213+
{ MP_ROM_QSTR(MP_QSTR_settimeout), MP_ROM_PTR(&micropython_ringbuffer_settimeout_obj) },
214+
{ MP_ROM_QSTR(MP_QSTR_flush), MP_ROM_PTR(&mp_stream_flush_obj) },
215+
{ MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_stream_read_obj) },
216+
{ MP_ROM_QSTR(MP_QSTR_readline), MP_ROM_PTR(&mp_stream_unbuffered_readline_obj) },
217+
{ MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) },
218+
{ MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) },
219+
{ MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&mp_stream_close_obj) },
220+
221+
};
222+
STATIC MP_DEFINE_CONST_DICT(micropython_ringbuffer_locals_dict, micropython_ringbuffer_locals_dict_table);
223+
224+
STATIC const mp_stream_p_t ringbuffer_stream_p = {
225+
.read = micropython_ringbuffer_read,
226+
.write = micropython_ringbuffer_write,
227+
.ioctl = micropython_ringbuffer_ioctl,
228+
.is_text = false,
229+
};
230+
231+
MP_DEFINE_CONST_OBJ_TYPE(
232+
mp_type_micropython_ringbuffer,
233+
MP_QSTR_ringbuffer,
234+
MP_TYPE_FLAG_NONE,
235+
make_new, micropython_ringbuffer_make_new,
236+
protocol, &ringbuffer_stream_p,
237+
locals_dict, &micropython_ringbuffer_locals_dict
238+
);
239+
240+
#endif

py/ringbuf.h

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,8 @@
2828

2929
#include <stddef.h>
3030
#include <stdint.h>
31-
32-
#ifdef _MSC_VER
33-
#include "py/mpconfig.h" // For inline.
34-
#endif
31+
#include "py/obj.h"
32+
#include "py/mpconfig.h"
3533

3634
typedef struct _ringbuf_t {
3735
uint8_t *buf;
@@ -96,4 +94,8 @@ int ringbuf_get16(ringbuf_t *r);
9694
int ringbuf_peek16(ringbuf_t *r);
9795
int ringbuf_put16(ringbuf_t *r, uint16_t v);
9896

97+
#if MICROPY_PY_MICROPYTHON_RINGBUFFER
98+
extern const mp_obj_type_t mp_type_micropython_ringbuffer;
99+
#endif
100+
99101
#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)
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

0 commit comments

Comments
 (0)
0