Closed MrSurly closed 7 years ago
What I have so far:
In general, the default values are shown in these examples.
import network
b = network.Bluetooth()
service_uuid = bytearray(range(16))
characteristic_uuid = bytearray([x * 3 for x in range(16)])
s = b.service(service_uuid, isPrimary = True)
s.add_characteristic(
characteristic_uuid,
permissions = b.PERM_READ | b.PERM_WRITE,
properties = b.PROP_READ | b.PROP_WRITE | b.PROP_NOTIFY)
These are constants found in class Bluetooth
These are constants found in class Bluetooth
b.ble_settings(
int_min = 1280,
int_max = 1280,
type b.ADV_TYPE_IND,
own_addr_type = b.BLE_ADDR_TYPE_PUBLIC,
peer_addr = bytearray([0 for x in range(6)]),
peer_addr_type = b.BLE_ADDR_TYPE_PUBLIC,
channel_map = b.ADV_CHNL_ALL,
filter_policy = b.ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
adv_is_scan_rsp = False,
adv_dev_name = None,
adv_man_name = None,
adv_inc_txpower = False,
adv_int_min = 1280,
adv_int_max = 1280,
adv_appearance = 0,
adv_uuid = None,
adv_flags = 0)
b.adv_enable(True)
@dpgeorge @mahanet
"network" module is unlikely a good place for native Bluetooth stuff. While not spelled explicitly, "network" is intended for IP-based networks. So, it may be a good place for 6LoWPAN support over BLE, but stuffing everything at all there doesn't sound right.
On the other hand, using "socket" paradigm for Bluetooth communication sounds interesting, but going that way would require extensive review of the prior art (e.g. how it works in Linux) to design good API.
Current progress at: https://github.com/MrSurly/micropython-esp32/tree/dev-bluetooth
It's a mess; still sorting out learning µPy, and BT.
@MrSurly your progress gives me hope :+1:
So yeah a mess but couldn't help trying it out. After flashing I tried the example (I believe you were suggesting a concept more than a working example). I think those are some typos- Service instead of service equal sign after type= (in call to ble_settings) adv_enable is present as ble_adv_enable
Apart from these, add_characteristic didn't accept keyword args.
Yeah, it's pretty broken; I'm only pushing commits for the sake of off-site backup.
I'm working on it right now.
On Tue, Apr 18, 2017 at 3:05 PM, mahanet notifications@github.com wrote:
@MrSurly https://github.com/MrSurly your progress gives me hope 👍
So yeah a mess but couldn't help trying it out. After flashing I tried the example (I believe you were suggesting a concept more than a working example). I think those are some typos- Service instead of service equal sign after type= (in call to ble_settings) adv_enable is present as ble_adv_enable
Apart from these, add_characteristic didn't accept keyword args.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/micropython/micropython-esp32/issues/68#issuecomment-294998003, or mute the thread https://github.com/notifications/unsubscribe-auth/AAYHCPvnfSD9SS-PEZxvNvsfESqqhE0xks5rxTPDgaJpZM4M3aEm .
Hi MrSurly,
I am interested in your progress on the Classic Bluetooth. I would like to contribute for the effort, but I will need some guidance. Would you be interested in having some help? Filipe
I am interested in your progress on the Classic Bluetooth. I would like to contribute for the effort, but I will need some guidance. Would you be interested in having some help?
@filipeaguiar22
Sure. Right now, I'm just focusing on BLE GATTS. How would you like to proceed? Have you already begun working on BT Classic?
Update: (comments welcome)
Right now, you can use the Bluetooth
(singleton) object to create Service
objects.
Service objects have an associated internal list/tuple called chars
, which is the characteristics associated with the service. Char
(acteristic) objects can be created separately, and attached to a Service
by updating Service.chars
. You need to call Service.stop()
then Service.start()
if you update characteristics.
There is a problem in the implementation detail. The IDF works on callbacks, so when you start a service, in the callback, the correct Service
object must be referenced, to update the handle, etc.
I could easily keep an internal reference cache to all the created Service
objects, so that I can look them up in the callback, but what if the Service
object in question is no longer in use in user-land Python code? This led to my recent question in the forum: "weak" reference to python object?, since weak references would solve this problem neatly.
Currently working, at an API level, if not quite yet internally:
import network as n
b = n.Bluetooth()
s1 = b.Service(uuid=0xaabb)
s2 = b.Service(uuid=0xaacc)
c1 = b.Char(0xcc)
c2 = b.Char(0xdd)
s1.chars = [c1]
s2.chars= [c2]
[s.start() for s in (s1,s2) ]
@MrSurly : You're trying to create an API and its usage model foreign to MicroPython principles. MicroPython uses simple and obvious explicit resource management model, where resource is allocated in constructor and freed in an explicit method call, usually named .close()
. If users don't call .close() when they finished working with a resource, they want a resource leak, and of course, they get what they want. Weak references, singletons for dynamically allocated objects - nothing of this is needed. The only programmer's responsibility is to make sure that object registered with external services is referenced from the memory area GC-scanned by uPy. When you can't ensure scanning of external services' memory, you indeed need to keep a container for the sole purpose of keeping the objects live, but the simplest container is a list, not a set.
Weakref support "discussion" (which is again, completely unneeded for the usecase) is at the expected place (which is search results in the main project): https://github.com/micropython/micropython/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aopen%20weakref
Well, uPy supports destructors (__del__
method) and its usage is actually a good alternative to keeping an explicit "liveness" container. So, instead of keeping reference to unclosed object forever, we actually close it automatically when it's GCed. That's how modlwip works - I almost had that "d'oh" moment wondering if we had liveness issues with it all this time.
But that's again object liveness management measure, not resource management measure - for I/O resources, relying on desctructors is a bad idea because it's not guaranteed when it will be called (or that it will be called at all). Again, explicit .close()
method is the only viable solution.
You're trying to create an API and its usage model foreign to MicroPython principles. MicroPython uses simple and obvious explicit resource management model, where resource is allocated in constructor and freed in an explicit method call, usually named .close(). If users don't call .close() when they finished working with a resource, they want a resource leak, and of course, they get what they want. Weak references, singletons for dynamically allocated objects - nothing of this is needed.
For BLE services, the IDF has a callback. The callback must have access to the service object data to properly manage the BLE stack. Here are two implementations:
Bluetooth.Service()
ctor) to the user.Bluetooth
singleton, and manage via methods attached to Bluetooth
Number 1 requires that the Bluetooth
singleton have access to the created objects when the callback is called. Calls to a close()
method for a Service
class would have to inform the Bluetooth
singleton that the object is no longer viable, and remove the internal reference; this is entirely do-able. Service
objects have to have a singleton nature for a given UUID, since the IDF treats them this way. Thus, creating a new Service
(with a given UUID) object when you haven't called close()
on an existing one (with the same UUID) would be a runtime error.
Number 2 seems overly clunky, as it makes sense to use an object heirarchy (Bluetooth --> Service --> Characteristic), rather than having a Bluetooth
god object.
I'm sure there are more implementation options I haven't considered. I welcome any concrete API examples you'd care to suggest, if it can work within the reality of how BLE & the ESP IDF operate.
IDF Constraints:
I welcome any concrete API examples you'd care to suggest,
Above is the pretty concrete example - modlwip, a module for native lwIP API. It has all traits of a mundane callback-based API: set up (like creation of objects) is initiated by client, but some events are delivered back to it via callbacks. Of course, callbacks have "user data" parameter, and that's what used to pass the original socket mp_obj_t into callbacks.
if it can work within the reality of how BLE & the ESP IDF operate.
Well, now if IDF does something completely different to the normal process describe above, then you please point to the definition of these callbacks in IDF headers.
If you really hint what you do, then perhaps you're indeed trying to over-engineer and option 2 is the most natural solution for an initial prototype. I personally can appreciate desire to build a Service object where there's none actually, but doing that may indeed require too much "impedance matching". Again, studying prior art of (apparently) simpler implementation like modlwip would show the idea how that should work and help to decide whether callbacks and overall API of IDF is suitable for something like that.
Above is the pretty concrete example - modlwip, a module for native lwIP API. It has all traits of a mundane callback-based API: set up (like creation of objects) is initiated by client, but some events are delivered back to it via callbacks. Of course, callbacks have "user data" parameter, and that's what used to pass the original socket mp_obj_t into callbacks.
The IDF has a single C callback for all GATTS, which includes events for creating services. It does not support a "user data" parameter. For service creation, it will return the service id (which includes UUID), and a handle. For other events related to services, it gives you this same handle (which is the BLE handle). Thus, you need to keep the original object ... somewhere.
Then call @projectgus and @igrr and pass them dearest hellos - they did it again!
Note that with
esp_err_t esp_ble_gatts_create_service(esp_gatt_if_t gatts_if,
esp_gatt_srvc_id_t *service_id, uint16_t num_handle);
it would work if only instead of
/**
* @brief ESP_GATTS_CREATE_EVT
*/
struct gatts_create_evt_param {
esp_gatt_status_t status; /*!< Operation status */
uint16_t service_handle; /*!< Service attribute handle */
esp_gatt_srvc_id_t service_id; /*!< Service id, include service uuid and other information */
} create;
it was:
struct gatts_create_evt_param {
esp_gatt_status_t status; /*!< Operation status */
uint16_t service_handle; /*!< Service attribute handle */
esp_gatt_srvc_id_t *service_id; /*!< Service id, include service uuid and other information */
} create;
i.e. if only event delivered via callback had a pointer to esp_gatt_srvc_id_t, as passed in from esp_ble_gatts_create_service(). Then you could subclass esp_gatt_srvc_id_t and add any additional data to it. But nope, doing it the most obvious way is not how Espressif folks do stuff.
So, @MrSurly, sure, there's no choice, you'll need to maintain a mapping from UUID to Service object, that will also take care about singleton'ness.
Hi @MrSurly ,
I'm not going to engage with Paul's comment directly because I don't think that attitude is conducive to a constructive or professional working relationship.
However, there's no reason why we can't expand the GATTS API to support the use case you have. I don't work on BLE directly, but if I understand correctly it may be enough to add a void *user_data
member to the esp_gatt_srv_id_t
structure? This way user data specified when the service is created will be accessible in the callbacks.
If you have a request of this kind, or a different suggestion, feel free to open an issue on the ESP-IDF github project - our Bluetooth developers are active on there and they may have other feedback or suggestions. Or pull requests are also welcome, if you have a modification to IDF that works for you.
Angus
Hi @projectgus,
Thanks for the heads up on modifying the IDF; didn't know that that was a likely possibility.
In any case, I need to keep an active μPy object because of the issues outlined above (especially singleton-ness), so in this specific case, there's no need. I think I have a good basis for going forward. Enough "analysis paralysis" =)
Having a userdata pointer might be useful for other code bases, but I suspect most of them are going to be more like the hard-coded service tables seen in the BT GATTS example in the IDF.
but if I understand correctly it may be enough to add a void *user_data member to the esp_gatt_srv_id_t structure?
@projectgus , thanks for your kind words. One of these days, someone with "constructive or professional working relationship" should make sure that every callback of something which aspires to be more or less general API, accepts a user data parameter, this is a very well known principle with no exceptions, and there's absolutely no need for threads like this to happen to prove it again and again.
Okay, V2.0:
Comments welcome
import network
b = network.Bluetooth()
service = b.Service(uuid, primary = True)
Services with the same UUID are the same object. Constructing a second
service with the same UUID will return the same service object. The exception
are services that have been closed (via the .close()
method) -- this
deallocates the service from the BLE stack.
service.callback(callback_func, user_data = None)
char service.Char(uuid, value = None, permissions = b.PERM_READ | b.PERM_WRITE, properties = b.PROP_READ | b.PROP_WRITE | b.PROP_NOTIFY)
Characteristics are associated with services.
service.start()
# stuff
service.stop()
# services can be restarted:
service.start()
service.close() # Will call stop()
service.start() # Runtime error; service is no longer usable
Closed service objects are completely "forgotten" by the BLE stack, you needn't keep reference. You will no longer receive callbacks for this service object or it's characteristics.
currentServices = b.services()
chars = service.chars()
b.ble_settings(
int_min = 1280,
int_max = 1280,
type b.ADV_TYPE_IND,
own_addr_type = b.BLE_ADDR_TYPE_PUBLIC,
peer_addr = bytearray([0 for x in range(6)]),
peer_addr_type = b.BLE_ADDR_TYPE_PUBLIC,
channel_map = b.ADV_CHNL_ALL,
filter_policy = b.ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
adv_is_scan_rsp = False,
adv_dev_name = None,
adv_man_name = None,
adv_inc_txpower = False,
adv_int_min = 1280,
adv_int_max = 1280,
adv_appearance = 0,
adv_uuid = None,
adv_flags = 0)
b.adv_enable(True)
Okay, now having a weird problem
The function below is used to find an existing service by UUID, for the reasons outlined above. It works fine, except when called from the gatts callback registered using esp_ble_gatts_register_callback
. The call to mp_iternext
fails with the exception seen below.
Is anyone aware of any possible problems of doing this stuff from the callback? The documentation doesn't mention anything, that I've found.
@projectgus Is there someone on the IDF team that might be able to comment?
STATIC network_bluetooth_service_obj_t* find_service(esp_bt_uuid_t* uuid) {
NETWORK_BLUETOOTH_DEBUG_PRINTF("find_service\n");
network_bluetooth_obj_t* bluetooth = network_bluetooth_get_singleton();
NETWORK_BLUETOOTH_DEBUG_PRINTF("bluetooth = %p\n", bluetooth);
NETWORK_BLUETOOTH_DEBUG_PRINTF("uuid = %p\n", uuid);
mp_obj_t iterable = mp_getiter(bluetooth->services, NULL);
NETWORK_BLUETOOTH_DEBUG_PRINTF("iterable = %p\n", iterable);
mp_obj_t item;
while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) {
NETWORK_BLUETOOTH_DEBUG_PRINTF("inside while\n");
network_bluetooth_service_obj_t* service = (network_bluetooth_service_obj_t*) item;
NETWORK_BLUETOOTH_DEBUG_PRINTF("before cmpare\n");
if(uuid_eq(uuid, &service->service_id.id.uuid)) {
return service;
}
}
return MP_OBJ_NULL;
}
STATIC network_bluetooth_service_obj_t* find_servicex(esp_bt_uuid_t* uuid) {
NETWORK_BLUETOOTH_DEBUG_PRINTF("find_servicex\n");
network_bluetooth_obj_t* bluetooth = network_bluetooth_get_singleton();
NETWORK_BLUETOOTH_DEBUG_PRINTF("bluetooth = %p\n", bluetooth);
NETWORK_BLUETOOTH_DEBUG_PRINTF("uuid = %p\n", uuid);
mp_obj_iter_buf_t iter_buf;
mp_obj_t iterable = mp_getiter(bluetooth->services, &iter_buf);
NETWORK_BLUETOOTH_DEBUG_PRINTF("iterable = %p\n", iterable);
mp_obj_t item;
NETWORK_BLUETOOTH_DEBUG_PRINTF("before mp_iternext\n");
item = mp_iternext(iterable);
NETWORK_BLUETOOTH_DEBUG_PRINTF("after mp_iternext\n");
return MP_OBJ_NULL;
}
network_bluetooth_service_start()
network_bluetooth_gatts_event_handler(event = 07 / CREATE, if = 04, param = (status = 00 / OK, service_handle = 0028, service_id = (id = (uuid = AA 00 , inst_id = 00), is_primary = true))
ESP_GATTS_CREATE_EVT
before find_service
find_servicex
bluetooth = 0x3ffc093c
uuid = 0x3ffe8706
iterable = 0x3ffe86b0
before mp_iternext
Guru Meditation Error of type LoadProhibited occurred on core 0. Exception was unhandled.
Register dump:
PC : 0x400dd086 PS : 0x00060830 A0 : 0x800dd0b2 A1 : 0x3ffe85e0
A2 : 0x3ffd99fc A3 : 0x3f440265 A4 : 0x3ffe8660 A5 : 0x3ffd99a4
A6 : 0x00000000 A7 : 0x00000000 A8 : 0x800dd086 A9 : 0x3ffe85c0
A10 : 0x00000000 A11 : 0x00000004 A12 : 0x00000001 A13 : 0x00000080
A14 : 0x00000001 A15 : 0x00000000 SAR : 0x00000004 EXCCAUSE: 0x0000001c
EXCVADDR: 0x0000000c LBEG : 0x400014fd LEND : 0x4000150d LCOUNT : 0xfffffffb
Backtrace: 0x400dd086:0x3ffe85e0 0x400dd0b2:0x3ffe8610 0x400dc40e:0x3ffe8630 0x400f1632:0x3ffe86b0 0x40105325:0x3ffe86e0 0x40105a60:0x3ffe8700 0x401030cb:0x3ffe8740
Rebooting...
ets Jun 8 2016 00:22:57
rst:0xc (SW_CPU_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0x00
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:2
load:0x3fff0008,len:8
load:0x3fff0010,len:2372
load:0x40078000,len:6708
load:0x40080000,len:252
entry 0x40080034
ets Jun 8 2016 00:22:57
rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0x00
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:2
load:0x3fff0008,len:8
load:0x3fff0010,len:2372
load:0x40078000,len:6708
load:0x40080000,len:252
entry 0x40080034
@MrSurly good to see you are making progress! The crash may be a problem with the callback code not being in iRAM. A lot of the IDF callbacks happen within an IRQ context and can only run from iRAM. I think you can configure this though so the callback is called after the IRQ returns, when flash is accessible again; you configure it when you set the callback.
Also note that mp_iternext may raise an exception (but not StopIteration, it'd be something else) and that would probably lead to a crash. To guard against that you'd need to wrap all the code in an nlr block; see py/runtime_utils.c for an example of how to do that.
If the list of services is always a list and not a generic iterable (I think that's a sensible restriction) then it's much easier to use mp_obj_get_array() to extract the items from the list.
@MrSurly good to see you are making progress! The crash may be a problem with the callback code not being in iRAM. A lot of the IDF callbacks happen within an IRQ context and can only run from iRAM. I think you can configure this though so the callback is called after the IRQ returns, when flash is accessible again; you configure it when you set the callback.
I don't really have that option: esp_err_t esp_ble_gatts_register_callback(esp_gatts_cb_t callback)
There isn't anything in the documentation about this code needing to be in iRAM, and none of the example code indicates that you need to do that.
I want to do more investigation -- I think it's more likely I'm just doing something wrong, and less likely it's something with the IDF/docs/examples. Although, the exact same code works fine when called outside the callback (i.e. when you try to add a duplicate service UUID).
I'm considering getting a JTAG -- have you used one with the ESP32?
I think a plan might be:
Also note that mp_iternext may raise an exception (but not StopIteration, it'd be something else) and that would probably lead to a crash. To guard against that you'd need to wrap all the code in an nlr block; see py/runtime_utils.c for an example of how to do that
I'll try it.
Thanks!
If the list of services is always a list and not a generic iterable (I think that's a sensible restriction) then it's much easier to use mp_obj_get_array() to extract the items from the list.
It's always a list. I'm mostly learning by looking at existent code, and I saw the mp_getiter
idiom often employed.
I believe that callback runs in the context of the Bluedroid task, not an ISR, so it shouldn't be a problem having it in flash cache not IRAM. (Side note: as it happens, in IDF most ISRs don't need to be in IRAM any more, see here.)
Looking at the exception dump, this looks to me like a null pointer dereference (exception type LoadProhibited, EXCVADDR 0xd - implies the pointer was to 0x0 and it's trying to read a structure member at offset 0xd).
You can decode the crashing address and the "Backtrace" line, to find where in the source this happens. The simplest way is to use the idf_monitor tool (which IDF runs when you invoke the "make monitor" target). There's some documentation here: http://esp-idf.readthedocs.io/en/latest/idf-monitor.html
I don't know how compatible that approach is with Micropython. Alternatively you can paste the backtrace line into addr2line. There's a sample command line at the documentation link above.
I'm considering getting a JTAG -- have you used one with the ESP32?
Regarding JTAG, there are still some quirks with openocd on ESP32 which makes it a little awkward to use sometimes. A simpler - and often just as workable - solution is to enable the "gdbstub on panic", where the ESP32 speaks the gdb protocol over serial after a fatal exception of this kind. This allows you to poke around with gdb, similar to loading a core dump on the desktop. You can set this option in make menuconfig
under "ESP32-Specific" -> "Panic Handler Behaviour". (I know Micropython doesn't use menuconfig directly, but that's the best explanation I have for how to find it.)
If you enable the serial gdbstub and use idf_monitor/"make monitor" then gdb will automatically be started when the gdbstub starts up on the ESP32. If you use a different serial program then you have to close the serial port and then run gdb manually. The manual gdb command can also be found in the documentation link above.
(If you do want a JTAG adapter, we mostly use FT2232H based ones like the "Tiao USB JTAG". I've also heard of people using J-Link. Any adapter supported by openocd in "JTAG mode" (ie not ST-Link, not CMSIS) should theoretically work.
Once you have a source code stack trace I'm happy to chase this up further on the IDF side. :)
@dpgeorge
JFYI, I've had to severely modify esp32/Makefile for BT. There are a lot of places where headers clash (e.g. too many incompatible config.h
files), so includes have been split up. Let me know if I'm going about this the wrong way.
Also, some parts of the IDF BT don't compile cleanly (usually strict aliasing), and I've added -Wno-error flags for the files in question.
Comments are very welcome. @mahanet, what is/are your use case(s)?
(As defined in the last RFC, above)
Also, some parts of the IDF BT don't compile cleanly (usually strict aliasing), and I've added -Wno-error flags for the files in question.
FWIW, this was recently fixed in IDF master branch (as of espressif/esp-idf#518).
Bluetooth Classic -- not even sure if the IDF supports this?
IDF master has support for A2DP as of last week. I believe other BT Classic profiles are coming.
@dpgeorge wrote:
The crash may be a problem with the callback code not being in iRAM. A lot of the IDF callbacks happen within an IRQ context and can only run from iRAM. I think you can configure this though so the callback is called after the IRQ returns, when flash is accessible again; you configure it when you set the callback.
I'm past where I was, but I've narrowed it down a bit. For my test, from within the callback, I'm simply doing:
mp_obj_print(mp_const_none, PRINT_REPR);
This crashes spectacularly, but if I modify obj.c
and remove the call to MP_STACK_CHECK();
from mp_obj_print_helper
, then the callback prints "None" as expected.
Looks like there's something about the µPy stack / threads that causes this crash when accessed from the callback.
Any thoughts?
I believe that callback runs in the context of the Bluedroid task, not an ISR, so it shouldn't be a problem having it in flash cache not IRAM. (Side note: as it happens, in IDF most ISRs don't need to be in IRAM any more, see here.) ...
@projectgus Thank you, this is very useful information.
JFYI, I've had to severely modify esp32/Makefile for BT
@MrSurly : You shouldn't need to modify it that much. For the following line you added:
$(BUILD)/$(ESPCOMP)/bt/%.o: CFLAGS = $(CFLAGS_BT)
you can just change that to append the include dirs to CFLAGS, eg:
$(BUILD)/$(ESPCOMP)/bt/%.o: CFLAGS +=
-I$(ESPCOMP)/bt/bluedroid/include \
-I$(ESPCOMP)/bt/bluedroid/stack/include \
-I$(ESPCOMP)/bt/bluedroid/osi/include \
etc. That should work and be more self-contained.
Looks like there's something about the µPy stack / threads that causes this crash when accessed from the callback.
Ok, I think I know what's going on (and thanks to @projectgus for the hint that "callback runs in the context of the Bluedroid task"): in the BT task the stack is different and so when uPy does a stack-depth check it fails because the top of the stack has moved. There are two ways to fix this:
Do minimal processing in the BT callback, in particular don't call any uPy code that does a stack check. Calling mp_obj_get_array should be ok. But if anything raises an exception it'll crash because the exception handler lives in the uPy task and you can't do a long jump from one stack to another. You can use mp_sched_schedule() to schedule a callback in the main uPy task.
Save the old stack extents in the BT callback, change them, then restore them at the end. This will allow the stack checker to work and you can call any code (but be careful you don't overflow the BT task's stack). To do this you'd need to do something like:
char *old_stack_top = MP_STATE_THREAD(stack_top);
size_t old_stack_limit = MP_STATE_THREAD(stack_limit);
MP_STATE_THREAD(stack_top) = &old_stack_top; // a but of a hack but should work ok
MP_STATE_THREAD(stack_limit) = 2048; // how much stack does the BT task have?
// do things
MP_STATE_THREAD(stack_top) = old_stack_top;
MP_STATE_THREAD(stack_limit) = old_stack_limit;
return;
@MrSurly Thank you for the great progress!
My usecase is mainly a server with services and chars, being able to advertise, connect and read\write the chars. Client is bonus for me.
I played a bit with fa8b776 on bluetooth-dev, noticed that every service created after the first one, is falsely detected as an existing service. I believe it has to do with "network_bluetooth_find_item" under "network_bluetooth_service_make_new", maybe because arg "handle" is always zero? (is "handle" member of newely created service object initiated by the event? ) Maybe I'm just doing something wrong?
@MrSurly : You shouldn't need to modify it that much. For the following line you added: ...
$(BUILD)/$(ESPCOMP)/bt/%.o: CFLAGS +=
etc. That should work and be more self-contained.
If CFLAGS
already contains all the paths needed to compile µPy, then BT won't compile. Problem is that there are include paths needed by the "main" µPy code that contain a config.h
file, and there are different paths used by BT that also contain a config.h
file. So, depending on your path ordering, one or the other won't compile, since it will be getting the wrong config.h.
Also, there are some includes that are used by both µPy and BT. Thus:
INC_MAIN
: include paths only needed by µPyINC_BT
: include paths only needed by BTINC_COMMON
: include paths needed by bothWhich leads to this:
CFLAGS_BT := $(CFLAGS) $(INC_COMMON) $(INC_BT)
CFLAGS += $(INC_COMMON) $(INC_MAIN)
Though looking at it now, I could clearly eliminate INC_COMMON
, and make it part of CFLAGS
.
Do minimal processing in the BT callback, in particular don't call any uPy code that does a stack check. Calling mp_obj_get_array should be ok. But if anything raises an exception it'll crash because the exception handler lives in the uPy task and you can't do a long jump from one stack to another. You can use mp_sched_schedule() to schedule a callback in the main uPy task
I came to the same realization, and this is exactly what I implemented; it's working now, but I need to test further.
I played a bit with fa8b776 on bluetooth-dev, noticed that every service created after the first one, is falsely detected as an existing service. I believe it has to do with "network_bluetooth_find_item" under "network_bluetooth_service_make_new", maybe because arg "handle" is always zero? (is "handle" member of newely created service object initiated by the event? ) Maybe I'm just doing something wrong?
You were doing it right, my logic was wrong:
Was: if ((uuid != NULL && uuid_eq(&service->service_id.id.uuid, uuid)) || service->handle == handle)
Now (64fee58): if ((uuid != NULL && uuid_eq(&service->service_id.id.uuid, uuid)) || (uuid == NULL && service->handle == handle))
Here's my current test script:
import network as n
b = n.Bluetooth()
b.ble_settings(adv_man_name = "mangocorp", adv_dev_name="mangoprod" )
b.ble_adv_enable(True)
s1 = b.Service(0xFFFF)
s2 = b.Service(0xEEEE)
c1 = s1.Char(0xAAAA)
c2 = s2.Char(0xAAAA)
s1.start()
s2.start()
def ccb(c, e, d, u):
print(c,e,d,u)
return 'kumquat'
c1.callback(ccb,'callback1')
c2.callback(ccb,'callback2')
I then use gatttool
:
$ gatttool -I -b 24:0A:C4:05:7E:E6
[24:0A:C4:05:7E:E6][LE]> connect
Attempting to connect to 24:0A:C4:05:7E:E6
Connection successful
[24:0A:C4:05:7E:E6][LE]> char-desc
handle: 0x0001, uuid: 00002800-0000-1000-8000-00805f9b34fb
handle: 0x0002, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0003, uuid: 00002a05-0000-1000-8000-00805f9b34fb
handle: 0x0014, uuid: 00002800-0000-1000-8000-00805f9b34fb
handle: 0x0015, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0016, uuid: 00002a00-0000-1000-8000-00805f9b34fb
handle: 0x0017, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0018, uuid: 00002a01-0000-1000-8000-00805f9b34fb
handle: 0x0019, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x001a, uuid: 00002aa6-0000-1000-8000-00805f9b34fb
handle: 0x0028, uuid: 00002800-0000-1000-8000-00805f9b34fb
handle: 0x0029, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x002a, uuid: 0000aaaa-0000-1000-8000-00805f9b34fb
handle: 0x002b, uuid: 00002800-0000-1000-8000-00805f9b34fb
handle: 0x002c, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x002d, uuid: 0000aaaa-0000-1000-8000-00805f9b34fb
[24:0A:C4:05:7E:E6][LE]> char-write-cmd 2a 010203
[24:0A:C4:05:7E:E6][LE]> char-write-cmd 2d 040506
[24:0A:C4:05:7E:E6][LE]>
And this is what I see from µPy (lots of debug output, but look at the callback outputs):
network_bluetooth_gatts_event_handler(event = 0E / CONNECT, if = 04, param = (conn_id = 0000, remote_bda = 5C:F3:70:7D:8A:D6, is_connected = true)
entering network_bluetooth_gap_event_handler()
entering network_bluetooth_gap_event_handler()
network_bluetooth_gatts_event_handler(event = 02 / WRITE, if = 04, param = (conn_id = 0000, trans_id = 00000001, addr = 5C:F3:70:7D:8A:D6, handle = 002A, offset = 0000, need_resp = false, is_prep = false, len = 0003, data = 01 02 03 )
data pushed
In data handler
Found 1 queue items
BTChar(uuid = AAAA, perm = 11, prop = 1A, handle = 002A) 2 bytearray(b'\x01\x02\x03') callback1
network_bluetooth_gatts_event_handler(event = 02 / WRITE, if = 04, param = (conn_id = 0000, trans_id = 00000002, addr = 5C:F3:70:7D:8A:D6, handle = 002D, offset = 0000, need_resp = false, is_prep = false, len = 0003, data = 04 05 06 )
data pushed
In data handler
Found 1 queue items
BTChar(uuid = AAAA, perm = 11, prop = 1A, handle = 002D) 2 bytearray(b'\x04\x05\x06') callback2
Ok I'm able to create multiple services now!
I'm using "nRF connect" app for android for testing the chars, but It fails connecting. Tried with gatttool but I'm not sure my PC Bluetooth supports BLE. The micropython prompt doesn't print out anything related to the connection attempts.
I'll try getting more information about my fails.
I'm using "nRF connect" app for android for testing the chars, but It fails connecting. Tried with gatttool but I'm not sure my PC Bluetooth supports BLE. The micropython prompt doesn't print out anything related to the connection attempts.
Yes, the IDF BLE connection thing seems a bit weird. When I use gatttool
to perform a connection, it states "connected." On the ESP32/µPy, there isn't any CONNECT event, just this (printed directly by the IDF): E (6085885) BT: btm_ble_resolve_random_addr_on_conn_cmpl unable to match and resolve random address
.
However, when I send a command (like reading a characteristic), that's when the IDF sends a connect event.
I'm not sure if it's a bug in the IDF
Okay, new problem; I'm getting crashes whenever I append to a list, but only in the IDF GAP callback handler.
Code is here: https://github.com/MrSurly/micropython-esp32/commit/7bb0328827a72485ad0027c39a20197104d552b2
I've taken the approach of having the callback simply append relevant information to a queue, and then calling mp_sched_schedule()
to have a different handler read this queue and call any user-define Python callbacks.
Implementation is done via a global µPy list; in the IDF callback handler, I create a list of parameters (mp_obj_new_list
), and append to the global list.
In the handler that calls the Python callbacks, it iterates through the entire list, and then zeroes it's length.
Accesses to the global list are guarded using a xSemaphoreTake
and xSemaphoreGive
.
mp_obj_list_append
from the IDF callback will result in the stack trace seen below, right after calling gc.collect()
So, there's something going on with the GC that I don't understand.
I think I'll just use a ring buffer of a union object that can contain the relevant raw data from the IDF callback, for now; essentially bypass any memory management.
>> gap append
>> cbQ Take
<< cbQ Take
Guru Meditation Error of type StoreProhibited occurred on core 0. Exception was unhandled.
Register dump:
PC : 0x4000c46c PS : 0x00060030 A0 : 0x800e0c04 A1 : 0x3ffe9d70
A2 : 0x00000004 A3 : 0x00000000 A4 : 0xfffffffc A5 : 0x00000004
A6 : 0x3f404b90 A7 : 0x0fffffff A8 : 0x800d321f A9 : 0x3ffe9d40
A10 : 0x00000000 A11 : 0x00000000 A12 : 0x00000001 A13 : 0x00000000
A14 : 0x00000000 A15 : 0x00000000 SAR : 0x0000001c EXCCAUSE: 0x0000001d
EXCVADDR: 0x00000004 LBEG : 0x4000c46c LEND : 0x4000c477 LCOUNT : 0x0ffffffe
Backtrace:
0x4000c46c: ?? ??:0
0x400e0c04: mp_obj_list_append at /home/epoulsen/workspaces/micropython-esp32/esp32/../py/objlist.c:240
0x400f1bc6: network_bluetooth_gap_event_handler at /home/epoulsen/workspaces/micropython-esp32/esp32/network_bluetooth.c:1563
0x40106294: btc_gap_ble_cb_to_app at /home/epoulsen/workspaces/esp-idf/components/bt/bluedroid/btc/profile/std/gap/btc_gap_ble.c:35
0x40106a06: btc_gap_ble_cb_handler at /home/epoulsen/workspaces/esp-idf/components/bt/bluedroid/btc/profile/std/gap/btc_gap_ble.c:707
0x40105503: btc_task at /home/epoulsen/workspaces/esp-idf/components/bt/bluedroid/btc/core/btc_task.c:84
Line 240 in mp_obj_list_append
is:
231: mp_obj_t mp_obj_list_append(mp_obj_t self_in, mp_obj_t arg) {
232: mp_check_self(MP_OBJ_IS_TYPE(self_in, &mp_type_list));
234: mp_obj_list_t *self = MP_OBJ_TO_PTR(self_in);
235: if (self->len >= self->alloc) {
236: self->items = m_renew(mp_obj_t, self->items, self->alloc, self->alloc * 2);
237: self->alloc *= 2;
238: mp_seq_clear(self->items, self->len + 1, self->alloc, sizeof(*self->items));
239: }
240: self->items[self->len++] = arg;
241: return mp_const_none; // return None, as per CPython
242:}
Okay, new problem; I'm getting crashes whenever I append to a list, but only in the IDF GAP callback handler.
If the IDF callback is a true IRQ (which it is) then you can't allocate or free memory in this callback. This is because the code that was interrupted may have been in the middle of an allocation and the GC is not reentrant.
I think I'll just use a ring buffer of a union object that can contain the relevant raw data from the IDF callback, for now; essentially bypass any memory management.
Yes this would be the way to do it.
If the IDF callback is a true IRQ (which it is)
I don't think this is the case here. Based on the backtrace above, the callback runs from btc_task
.
(The rest of the comment about interrupting an allocation in progress is still valid)
If the IDF callback is a true IRQ (which it is) then you can't allocate or free memory in this callback.
From the IDF side, it should be OK to perform libc-level memory operations here as the BT event callbacks aren't interrupts, they run in the context of the bluedroid task started in btc_task.c (see the backtrace in the dump above.) [^]
I don't know anything about the Micropython side. Are there any restrictions on concurrently allocating/freeing memory, or accessing GC-managed objects from multiple tasks?
Store Prohibited at EXCVADDR 0x4 implies (I think) that "self" may be NULL, although the field at offset 4 appears to be "alloc" so the crash may actually be at line 237 (addr2line seems to get it a bit wrong sometimes due to optimisations, or possibly I'm misreading the code.)
[^]: For the record, it's probably better not to block that bluedroid task for an extended period, as the queue of pending BT notifications will eventually fill up - although priority inheritance should kick in and prevent absolute resource starvation if all the tasks involved are blocking on RTOS primitives. Anyway, I would think that short delays (blocking on RTOS primitives) are fine.
EDIT: Sorry, I missed the very clear "the GC is not reentrant" part of damien's post. So this is comment is mostly irrelevant!
Oops, my post crossed with Ivan's!
Thanks @igrr and @projectgus for the hints. Memory can only be allocated if control is within the main MicroPython task and an IRQ is not being processed. As said above, this is because the GC is not reentrant.
In the case here for BT, one cannot allocate memory from the btc_task (because the preemptive task switch may have interrupted a GC memory allocation).
@MrSurly a neat way of getting around this problem is to acquire the GIL before you do any memory allocations, because that will ensure you have exclusive access to the GC. Do something like this in the callback:
MP_THREAD_GIL_ENTER();
// code that may allocate memory
MP_THREAD_GIL_EXIT();
That will work quite efficiently, and the btc_task will block (via FreeRTOS's mutex's) if the GIL is held by the main uPy task. The main task will release the GIL without too much delay so btc_task won't block for long.
Updated API
import network
b = network.Bluetooth()
service = b.Service(uuid, primary = True)
Services with the same UUID are the same object. Constructing a second
service with the same UUID will return the same service object. The exception
are services that have been closed (via the .close()
method) -- this
deallocates the service from the BLE stack.
char service.Char(uuid, value = None, permissions = b.PERM_READ | b.PERM_WRITE, properties = b.PROP_READ | b.PROP_WRITE | b.PROP_NOTIFY)
Characteristics are associated with services.
service.start()
# stuff
service.stop()
# services can be restarted:
service.start()
service.close() # Will call stop()
service.start() # Runtime error; service is no longer usable
Closed service objects are completely "forgotten" by the BLE stack, you needn't keep reference. You will no longer receive callbacks characteristics attached to the service.
currentServices = b.services()
chars = service.chars()
def bt_callback(btObj, event, data, userdata):
print ("Bluetooth object", btObj)
if event == btObj.CONNECTED:
print ("connected")
if event == btObj.DISCONNECTED:
print ("disconnected")
Bluetooth.callback(bt_callback, "hello")
Bluetooth.callback
takes two parameters: The callback function itself, and an optional userdata value to be passed in on callbacks. It returns the current callback function and usedata as tuple.
cb_function
can be None, which disables callbacks. cb_function
is called with four parameters; the bluetooth object, event, event data, and the userdata.
Event values:
Bluetooth.CONNECT
Data param is remote addrBluetooth.DISCONNECT
Bluetooth.SCAN_DATA
Scan dataBluetooth.SCAN_CMPL
Scan completedef char_callback(charObj, event, value, userdata):
print ("Char object", charObj)
if event == btObj.CONNECT:
print ("connected")
if event == btObj.DISCONNECT:
print ("disconnected")
Char.callback(cb_function, userdata)
Char.callback
takes two parameters: The callback function itself, and an optional userdata value to be passed in on callbacks. It returns the current callback function and userdata as tuple.
cb_function
can be None, which disables callbacks. cb_function
is called with four parameters; the characteristic object, the event type, the event value (the sent value for WRITE
or the current characteristic value for READ
), and userdata.
The event type will be one of Bluetooth.READ
or Bluetooth.WRITE
.
Characteristics have an internal value, which is not used and not updated when there is a callback, though the callback is free to update or use the value as it sees fit. For READ
operations (or WRITE
operations that require a return value) the return value of the callback is sent, which must be a bytearray
, str
, or None
.
If there is no callback defined for a characteristic, it's internal value will be used for READ
or WRITE
events, and WRITE
events that expect a response will simply return the just-written value.
b.ble_settings(
int_min = 1280,
int_max = 1280,
type b.ADV_TYPE_IND,
own_addr_type = b.BLE_ADDR_TYPE_PUBLIC,
peer_addr = bytearray([0 for x in range(6)]),
peer_addr_type = b.BLE_ADDR_TYPE_PUBLIC,
channel_map = b.ADV_CHNL_ALL,
filter_policy = b.ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
adv_is_scan_rsp = False,
adv_dev_name = None,
adv_man_name = None,
adv_inc_txpower = False,
adv_int_min = 1280,
adv_int_max = 1280,
adv_appearance = 0,
adv_uuid = None,
adv_flags = 0)
b.adv_enable(True)
Advertising and scanning cannot be run at the same time
Bluetooth.scan_start(timeout)
Bluetooth.scan_stop()
The Bluetooth callback will receive:
Bluetooth.SCAN_DATA
Data will be a 2-tuple of (remote_address, adv_name)
Bluetooth.SCAN_CMPL
Scan has completed; data will be NoneGetting better here on c505d56 with an Iphone.
For my Ubuntu - there is no hardware BLE support. The nordic nrfConnect app for android fails on connecting (although it operates good on IDF bluetooth apps). But the same nordic app for Iphone works!
I can connect, view the services, chars, but when I try reading writing to them the whole thing fails - disconnects or throws a processor exception - I guess things are still under dev? Or should reading/writing be working?
Concerning the RFC, minor typos (for those who try to copy-paste from it)-
The script I played with-
import network
def bt_callback(btObj, event, data, userdata):
print ("Bluetooth object", btObj)
if event == btObj.CONNECT:
print ("connected")
if event == btObj.DISCONNECT:
print ("disconnected")
def char_callback(charObj, event, value, userdata):
print ("Char object", charObj)
if event == b.READ:
print ("read")
if event == b.WRITE:
print ("write")
b = network.Bluetooth()
b.ble_settings(adv_man_name = "mangocorp", adv_dev_name="mangoprod")
b.callback(bt_callback, "hello")
s1 = b.Service(uuid=0xaabb)
s2 = b.Service(uuid=0xa123)
c1 = s1.Char(0xcc, 15, b.PERM_READ | b.PERM_WRITE, b.PROP_READ | b.PROP_WRITE | b.PROP_NOTIFY)
c2 = s2.Char(0xdd, 16, b.PERM_READ | b.PERM_WRITE, b.PROP_READ | b.PROP_WRITE | b.PROP_NOTIFY)
c1.callback(char_callback, "char1 hello")
c2.callback(char_callback, "char2 hello")
s1.start()
s2.start()
b.ble_adv_enable(True)
The faults I was talking about probably had to do with chars values. Proper values should be strings or arraybytes but when I try either one I get an hard exception and a reset (on connection or read\write from chars).
When I use plain numbers I just get this output and no callback is called when reading writing from client to board-
network_bluetooth_gatts_event_handler(event = 0E / CONNECT, if = 04, param = (conn_id = 0000, remote_bda = 4B:A1:D1:9A:EB:18, is_connected = true)
>> gatts append
>> cbQ Take
<< cbQ Take
<< cbQ Give
>> cbQ Give
<< gatts append
>> handler
>> cbQ Take
<< cbQ Take
<< cbQ Give
>> cbQ Give
Bluetooth object Bluetooth(params=())Bluetooth(conn_id = 0000, gatts_connected = True, adv_params = (adv_int_min = 1280,
adv_int_max = 1280,
adv_type = 0,
own_addr_type = 0,
peer_addr = 00:00:00:00:00:00,
peer_addr_type = 0,
channel_map = 7,
adv_filter_policy = 0
), data = (set_scan_rsp = false,
include_name = true,
include_txpower = false,
min_interval = 2048,
max_interval = 2048,
appearance = 0,
manufacturer_len = 9,
p_manufacturer_data = mangocorp,
service_data_len = 0,
p_service_data = E (100286) BT: MTU request PDU with MTU size 517
nil,
flag = 0
)
connected
E (100296) BT: Call back not found for application conn_id=3
E (100306) BT: Call back not found for application conn_id=5
network_bluetooth_gatts_event_handler(event = 04 / MTU, if = 04, param = (conn_id = 0000, mtu = 0205)
>>>
>>>
>>> E (120506) BT: bta_gattc_conn_cback() - cif=3 connected=0 conn_id=3 reason=0x0013
E (120506) BT: bta_gattc_conn_cback() - cif=5 connected=0 conn_id=5 reason=0x0013
E (120506) BT: btm_sec_disconnected clearing pending flag handle:0 reason:19
For my Ubuntu - there is no hardware BLE support.
I ended up buying one of these: https://www.amazon.com/gp/product/B009ZIILLI/ref=oh_aui_detailpage_o08_s00?ie=UTF8&psc=1 Works fine on Ubuntu
Getting better here on c505d56 with an Iphone
I'm not going to respond to your individual points here. On Monday, I'll work through what you wrote, and try to reproduce/fix the issues.
Thank you for taking the time to try everything. If you're good with how the API doc is written, then I'll make the code conform. Changes/suggestions are welcome.
I'm not going to respond to your individual points here. On Monday, I'll work through what you wrote, and try to reproduce/fix the issues.
Thank you
If you're good with how the API doc is written, then I'll make the code conform.
These changes are very minor so both the current API and RFC are good for me. I just wanted to have you noticed there are small differences so the next RFC you post will correlate 100% :)
@mahanet https://github.com/MrSurly/micropython-esp32/commit/cc14148d26edafad407b680ce2e31c368fc726b2 has two fixes:
Char
constructor now takes kw argsI'm working on fixing up the RFC, your comments are all spot-on.
Here's what I'm using for boot.py
(below). You can simply type gatts()
in the µPy console to start a BLE server.
import network as n
import gc
b = n.Bluetooth()
def bcb(b,e,d,u):
print('BT callback: ', gc.mem_free(), end = '')
if e == b.CONNECT:
print("CONNECT")
elif e == b.DISCONNECT:
print("DISCONNECT")
elif e == b.SCAN_DATA:
if type(d) is tuple:
adx, name = d
print ('Found:' + ':'.join(['%02X' % i for i in adx]), name)
else:
print()
elif e == b.SCAN_CMPL:
print("Scan Complete")
else:
print ('Unknown event', e,d)
def cb (cb, event, value, userdata):
print('charcb ', cb, userdata,' ', end='')
if event == b.READ:
print('Read')
return 'ABCDEFG'
elif event == b.WRITE:
print ('Write', value)
def gatts():
s = b.Service(0xaabb)
c = s.Char(0xccdd)
c.callback(cb)
s.start()
b.ble_settings(adv_man_name = "mangocorp", adv_dev_name="mangoprod")
b.ble_adv_enable(True)
b.callback(bcb)
@dpgeorge
Edit: Looking into FreeRTOS queues. Edit 2: Problem is that there's no RAM left
Would MP_THREAD_GIL_ENTER();
work if what's being called ends up causing garbage collection?
I'm getting this stack trace, from inside an MP_THREAD_GIL_ENTER
block (below).
Re-implemented the ring buffer for event queue as doubly linked list, thus could be more easily dynamically sized. Turns out that doing BLE scans (in my office) causes a ridiculous amount of found BLE devices. Of course most of the events are simply duplicates of the same advertising packets.
To combat this, I created a global mp_set_t
for "seen / not seen", using the bluetooth address as the key, and only enqueueing events for devices not yet seen.
For longer scans, this eventually triggers a GC, since I'm creating new string objects to pass on to mp_set_lookup
.
0x400ef190: gc_collect_inner at /home/epoulsen/workspaces/micropython-esp32/esp32/gccollect.c:52
0x400ef178: gc_collect_inner at /home/epoulsen/workspaces/micropython-esp32/esp32/gccollect.c:44
0x400ef178: gc_collect_inner at /home/epoulsen/workspaces/micropython-esp32/esp32/gccollect.c:44
0x400ef178: gc_collect_inner at /home/epoulsen/workspaces/micropython-esp32/esp32/gccollect.c:44
0x400ef178: gc_collect_inner at /home/epoulsen/workspaces/micropython-esp32/esp32/gccollect.c:44
0x400ef178: gc_collect_inner at /home/epoulsen/workspaces/micropython-esp32/esp32/gccollect.c:44
0x400ef178: gc_collect_inner at /home/epoulsen/workspaces/micropython-esp32/esp32/gccollect.c:44
0x400ef178: gc_collect_inner at /home/epoulsen/workspaces/micropython-esp32/esp32/gccollect.c:44
0x400ef178: gc_collect_inner at /home/epoulsen/workspaces/micropython-esp32/esp32/gccollect.c:44
0x400ef1b8: gc_collect at /home/epoulsen/workspaces/micropython-esp32/esp32/gccollect.c:65
0x400d3908: gc_alloc at /home/epoulsen/workspaces/micropython-esp32/esp32/../py/gc.c:446
0x400d31fc: m_malloc at /home/epoulsen/workspaces/micropython-esp32/esp32/../py/malloc.c:76
0x400e27e9: mp_obj_new_str_of_type at /home/epoulsen/workspaces/micropython-esp32/esp32/../py/objstr.c:1976
0x400e30d4: mp_obj_new_str at /home/epoulsen/workspaces/micropython-esp32/esp32/../py/objstr.c:2034
0x400f1132: network_bluetooth_gap_event_handler at /home/epoulsen/workspaces/micropython-esp32/esp32/network_bluetooth.c:1691
0x40106820: btc_gap_ble_cb_to_app at /home/epoulsen/workspaces/esp-idf/components/bt/bluedroid/btc/profile/std/gap/btc_gap_ble.c:35
0x40106f92: btc_gap_ble_cb_handler at /home/epoulsen/workspaces/esp-idf/components/bt/bluedroid/btc/profile/std/gap/btc_gap_ble.c:707
0x40105a8f: btc_task at /home/epoulsen/workspaces/esp-idf/components/bt/bluedroid/btc/core/btc_task.c:84
Code is here: https://github.com/MrSurly/micropython-esp32/commit/eb69763b7e6e6a21f903c87bfbc999a8bdbfff53
@mahanet Also implmented indication/notification for characteristics in latest commit.
Well,
Things have come to a grinding halt.
Been seeing a lot of weird memory issues lately, and today I finally figured it out. I had already lowered the µPy heap size to 16Kib in order to get µPy to work at all when BT is enabled, and I lowered it again to 12Kib.
The good news is the weird memory problems went away. Bad news is that even _boot.py
dies with MemoryError: memory allocation failed, allocating 4212 bytes
.
Putting here to track BLE support, and to gather comments for API implementation.