Timer creation sporadically fails
Hi there,
I use the following code (snippet) after setting up the connection to a MetaMotionR and after initializing it to then activate some modules and to setup some processors and timers:
mbl_mw_metawearboard_tear_down(m_metawear_board);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
mbl_mw_settings_set_connection_parameters(m_metawear_board, 7.5, 7.5, 0, 6000);
switch_dc_promise = std::promise<bool>();
auto switch_future = switch_dc_promise.get_future();
auto switch_signal = mbl_mw_switch_get_state_data_signal(m_metawear_board);
mbl_mw_dataprocessor_passthrough_create(switch_signal, MBL_MW_PASSTHROUGH_MODE_COUNT, 0, nullptr,
C_MWC::c_switch_dc_recorded);
switch_future.wait();
switch_processor_promise = std::promise<bool>();
auto switch_processor_future = switch_processor_promise.get_future();
if (switch_dc_processor != nullptr) {
mbl_mw_event_record_commands(switch_dc_processor);
mbl_mw_led_stop_and_clear(m_metawear_board);
mbl_mw_event_end_record(switch_dc_processor, nullptr,
C_MWC::c_switch_processor_recorded);
switch_processor_future.get();
}
timer_promise = std::promise<int>();
auto timer_future = timer_promise.get_future();
auto batt_signal = mbl_mw_settings_get_battery_state_data_signal(m_metawear_board);
mbl_mw_datasignal_subscribe(batt_signal, nullptr, C_MWC::c_handle_battery);
mbl_mw_timer_create_indefinite(m_metawear_board, 500, 0, nullptr, C_MWC::c_timer_created);
timer_future.get();
mbl_mw_datasignal_read(batt_signal);
disconnect_promise = std::promise<bool>();
auto disconnect_future = disconnect_promise.get_future();
// Setup disconnect event to stop fusion and set led to red
MblMwEvent *dc_event = mbl_mw_settings_get_disconnect_event(m_metawear_board);
mbl_mw_event_record_commands(dc_event);
mbl_mw_sensor_fusion_stop(m_metawear_board);
mbl_mw_sensor_fusion_clear_enabled_mask(m_metawear_board);
mbl_mw_gyro_bmi160_stop(m_metawear_board);
mbl_mw_gyro_bmi160_disable_rotation_sampling(m_metawear_board);
mbl_mw_acc_stop(m_metawear_board);
mbl_mw_acc_disable_acceleration_sampling(m_metawear_board);
auto timer = mbl_mw_timer_lookup_id(m_metawear_board, m_battery_id);
if (timer != nullptr) {
mbl_mw_timer_stop(timer);
mbl_mw_timer_remove(timer);
}
if (switch_dc_processor != nullptr) mbl_mw_dataprocessor_passthrough_set_count(switch_dc_processor, 1);
mbl_mw_event_end_record(dc_event, nullptr, C_MWC::c_disconnect_recorded);
disconnect_future.get();
I use the promises to synchronize the asynchronous callbacks and the callbacks are handled in a friend class with static methods and a static reference to the class setting everything up, so that I have access to the members from there.
The callbacks of the snippet above are the following:
void C_MWC::c_switch_dc_recorded(void *context, MblMwDataProcessor *processor) {
this_->switch_dc_recorded(processor);
}
void switch_dc_recorded(MblMwDataProcessor *pProcessor) {
switch_dc_processor = pProcessor;
switch_dc_promise.set_value(true);
}
void C_MWC::c_switch_processor_recorded(void *context, MblMwEvent *event, int32_t status) {
this_->switch_processor_recorded(event, status);
}
void switch_processor_recorded(MblMwEvent *pEvent, int32_t status) {
switch_processor_promise.set_value(status == 0);
}
void C_MWC::c_timer_created(void *cointext, MblMwTimer *timer) {
this_->timer_created(timer);
}
void timer_created(MblMwTimer *pTimer) {
if (pTimer != nullptr) {
m_battery_timer = pTimer;
m_battery_id = mbl_mw_timer_get_id(m_battery_timer);
auto battery_signal = mbl_mw_settings_get_battery_state_data_signal(m_metawear_board);
mbl_mw_event_record_commands((MblMwEvent *) pTimer);
mbl_mw_datasignal_read(battery_signal);
mbl_mw_event_end_record((MblMwEvent *) pTimer, nullptr, C_MWC::c_cmds_recorded);
} else {
m_logger.debug("Received a nullptr in timer_created");
cmds_recorded(nullptr, TIMER_CREATION_ERROR_NULLPTR);
}
}
void C_MWC::c_cmds_recorded(void *context, MblMwEvent *evt, int32_t status) {
this_->cmds_recorded(evt, status);
}
void cmds_recorded(MblMwEvent *pEvent, int32_t status) {
switch (status) {
case MBL_MW_STATUS_OK:
m_logger.debug("battery timer setup done!");
mbl_mw_timer_start((MblMwTimer *) pEvent);
break;
case MBL_MW_STATUS_ERROR_TIMEOUT:
m_logger.debug("timed out on recording battery reading command!");
break;
case TIMER_CREATION_ERROR_NULLPTR:
m_logger.debug("was not able to get a valid timer pointer!");
break;
default:
m_logger.debug("was not able to setup battery timer [UNKNOWN]");
break;
}
timer_promise.set_value(status);
}
void C_MWC::c_disconnect_recorded(void *context, MblMwEvent *event, int32_t status) {
this_->disconnect_recorded(event, status);
}
void disconnect_recorded(MblMwEvent *pEvent, int32_t status) {
if (status == MBL_MW_STATUS_OK) {
disconnect_promise.set_value(true);
} else {
if (status == MBL_MW_STATUS_ERROR_TIMEOUT) m_logger.debug("[TIMEOUT]");
disconnect_promise.set_value(false);
}
}
On the first connection to a sensor this is working, all the events and processors are created successfully. Unfortunately, after some successful connections, I am unable to get a valid timer when executing
mbl_mw_timer_create_indefinite(m_metawear_board, 500, 0, nullptr, C_MWC::c_timer_created);
In the callback I only get a nullptr as the MblMwTimer* parameter.
Currently I can only resolve this by resetting the sensor. Afterwards I get a valid timer on the first connection, and start getting nullptrs after some connections (sometimes, but not always, as soon as the second one).
All the other processors are still created successfully, and I can also use sensor fusion, the switch module, the vibration motor and the LED. Only the timer I create to periodically check the current battery is not working.
Can you see some any mistakes I made when setting this up? Is there anything I can do to mitigate that problem?
Thank you!
Marcus
Comments
I forgot to mention that I remove the created timers and processors before disconnecting, e.g. to remove the mentioned battery timer I use:
What is the id of the timers that are created prior to receiving a nullptr (
mbl_mw_timer_get_id
)?Can you confirm that the remove command is being received by the device before the script terminates?
HI, sorry for not replying sooner.
I read out the IDs I receive before getting a nullptr and got the following:
As the IDs increased every time and as you asked me to confirm, that the timer is actually removed before the sensor is disconnected, I had a look again at the code, where the timer is removed.
As I posted before, the code was:
I included some additional logging and received the following outputs:
So the problem seems to be, that I receive a nullptr on
mbl_mw_timer_lookup_id
and am therefore not removing the timer. As a workaround I will not request it before removing but instead remember it after creation, but what might be the problem here?I think I found the problem.
I am setting up a disconnect handler on board, that disables the modules I activate, as well as trying to remove any timers and processors.
Currently the following code is executed:
If I remove the line with
mbl_mw_timer_remove(timer);
from this code block, leaving only thembl_mw_timer_stop(timer);
everything then works as expected. I receive a valid timer during the actual disconnect usingmbl_mw_timer_lookup_id
and on the next connection I receive the same timer ID, which I guess means, that the timer was actually removed.I therefore assume, that by calling
mbl_mw_timer_remove
during thembl_mw_event_record_commands
I somehow invalidate the timer? Am I actually removing it from the board during the recording?Unfortunately, the timer is now of course no longer removed on an unexpected disconnect in the disconnect handler. So after a connection loss I get a timer with a higher ID.
Is there some way to remove timers (and processors I guess) during the registering of a disconnect handler without making them invalid?
I am also wondering about the effect of the
mbl_mw_metawearboard_tear_down
I am doing directly after connecting to the board.In the API it is described as
But it seems not to remove any stale timers. Do I have to use it differently?
mbl_mw_metawearboard_tear_down
only removes timers it is aware of; it is meant to be called at the end of a script.You already have the timer pointer so just use
m_battery_timer
directly instead of looking it up again by id.Given that you're running so much code to stop everything, just reset the board on disconnect.
Hi,
sorry to reopen this stale thread, but I have question more or less regarding the topics already discussed here.
Currently we chose to follow your suggestion and to just reset the board on disconnect to remove all timers and processors should we loose the connection.
We do this as we still face the problem that removing timers or dataprocessors during the disconnect event recording results in them being removed instantly, not after a disconnect, which makes this approach unusable.
But we are currently thinking about implementing a charging indicator for disconnected boards that would require a registered handler for the charge_status_data_signal that survives a disconnect.
Is it possible to setup something like this?
In detail we would need the following:
Program the charge events with the macro module
Hi Eric,
I setup a process to program charge events (as well as writing calibration data for sensor fusion) using the macro module as you advised.
This is working, in that I receive a valid macro ID and it seems these macros also survive a disconnect, as I receive a higher macro ID when I try to store a new one after disconnecting.
(so the first macro recording results in macro ID 0, after a disconnect the next recording results in macro ID 1, and so on)
But it seems the recordings do not survive a board debug reset.
If I not only disconnect but reset between flashing a macro, I always get macro ID 0.
So I assume the macro module is in fact not a solution to the problem I outlined before?
Post the macro setup code that you believe is failing.
Hi,
I have the following code in an Android App that tries to write previously stored calibration data and a (simple, not yet really functional) reaction to a power status change as a macro
As I mentioned before, the first time I execute this I get a log with
If I execute the same method again without calling
macro.eraseAll()
in between I getThis is the behaviour I expected.
My problem is that when I write a macro (ID = 0) and then debug reset the sensor, this macro is presumably also deleted, as I get
Macro ID = 0
again the next time I try to write one.Currently we are calling debug reset in the onboard disconnect handler, as we are otherwise unable to reliably clean up all stored timers and dataprocessors (as I described in my previous posts).
The reset is not issued inside the macro.
Our current setup is the following:
the reset is issued from the disconnect handler we setup from a c++ library we use to actually interact with our sensors (data streaming, etc.), the code for that is
We shortly tried the macro with the code I posted above, but we did not see any LED reaction. But we stopped trying when we realized that the macros seem to be deleted.
Sorry to bump this, but do you have any more feedback regarding the topic?
Especially regarding:
Yes. If it wasn't, MetaBase wouldn't work.
You can confirm this by having a macro turn on the LED at boot, then reset the board. The LED will turn on once reset.
I just tried this.
I wrote a macro that turns on the LED on boot (much like the example from the android docs) and it worked after putting the device to sleep (I used metabase, so I assume it uses enablePowersave) and rebooting with the button.
But if I issue a debug reset (also using metabase, I assume using resetAsync) afterwards and put it to sleep after reconnecting, the LED will not turn on when pressing the button (and I also get macro ID = 0 again).
MetaBase clears everything with that reeset button; what you are describing is the expected behavior.
I am talking about simple reset, no macro or log clears.
Thank you for clearing up the confusion, I assumed that the reset in MetaBase would only issue a simple reset.
When not using MetaBase reset but only a "simple" reset, this actually works and the macro survives.
One follow-up question though:
Is it possible to manipulate routes or dataprocessors set up in a macro from an application that did not create that macro?
I ask because the macro we set up changes the LED based on whether a power source is attached and whether the battery is currently being charged or not.
But of course this behaviour now also persists when actually using the sensor, which is something we would like to avoid.
I was hoping to maybe use a dataprocessor with a modifyable signal like a comparator to change that processor once connected and thereby effectively "disabling" the route. But when I connect the sensor and issue a mbl_mw_dataprocessor_lookup_id with ID 0 (assuming that would be one of the dataprocessor IDs created in the macro) without having created any dataprocessors in that (C++) application I only get a SIGABRT beause of 'std::out_of_range' from _Map_base::at at dataprocessor.cpp:592
Is there any other way to modify the internal routes set up in the macro?
And also: shouldn't the lookup_id return a nullptr when no object can be found (like mentioned in the docs)?
The only cross device usage we support is downloading logged data. You could do it if the applications are using the same SDK but not in the general case.
No
Yes it should. You can catch the exception as a temporary workaround.
Alright, thanks
Can you tell me more about that? Would it be possible to change something in the processors if I set up the macro using the C++ SDK? Or is the same SDK the requirement for downloading log data?
You can manipulate the board however you want if you are using the same SDK.
Downloading logged data is the only universal thing you can do regardless of SDK.
I did replicate the macro setup using the C++ SDK to try and change the dataprocessors I created.
The code for setting up the macro is the following (sorry for the long lines, it was easiest to just chain the callbacks):
This is working in so far, as that the LED is reacting to plugging in a power source as intended.
I still have the problem, that I can't "deactivate" these routes once I connect the sensor again afterwards and I tried to do what I initially planned to do and change the comparator dataprocessors.
But a mbl_mw_dataprocessor_lookup_id with ID 0 on a fresh connection still results in a nullptr / out_of_range.
Is it not possible to actually check for exisiting dataprocessors? Do I have to "remember" them after setting them up and just assume they are there?
Yes, you have to remember them between runs. That is what serialization is for.
https://mbientlab.com/cppdocs/0/advanced_features.html#serialization
Understood.
But is there any way to find out after connecting a sensor whether any macros were already setup on that specific device?
I am asking because we want to ensure backwards compatibility of our software to sensors we did not yet setup with the charging macros.
No, there is not.
You can have the boards with the new setup retain some special state that your app can use distinguish between the two. The debug module has a 4 byte temp variable that you can use to write whatever value you want to it.
mbl_mw_debug_get_key_register_data_signal
mbl_mw_debug_set_key_register
Thanks, I'll have a look into that!