Mbuf_printf and documentation troubles

My goal is: Want to print to mbuf using syntax like json_printf()
My expectation & question is: Is there a better way to format large amounts of input in an mbuf for mgos_shadow_updatef()

Hey guys. I’m working on creating a device that uploads data to an AWS shadow, but I’m wondering if there’s a more convenient way to package my data using the c mbuf structs.

I can create a device shadow on AWS and I feel like I understand how updating that shadow works- I’ve been working off the mongoose-os-smart-light example and have had success using the mgos_shadow_updatef() function to update my shadow.

(As a note, the AWS API reference does not detail this function on the website, you need to visit the github repo to understand how it works or that it exists. It would be really nice if this was just on the API reference page.)

However, I’ve found this syntax to be a little lacking for larger shadow profiles. For example, in the mongoose-os-smart-light example, you have this great-looking callback:

static void connected_cb(int ev, void *ev_data, void *userdata) {
  /* this syntax is great when you have one or two variables */
  mgos_shadow_updatef(0, "{on: %B}", s_light_on); /* Report status */
  (void) ev;
  (void) ev_data;
  (void) userdata;
}

mgos_shadow_updatef() is really easy to use when you have one or two variables, but what about when you have more that you want to include in your shadow update?

/* this is a little less convenient */
mgos_shadow_updatef(0, "awayDelay: %B, isArmed: %B, valveStatus: %d, schedules: {mode: %d, s1: {en: %B, startHour: %d,startMin: %d,endHour: %d,endMin: %d},s2: {en: %B, startHour: %d,startMin: %d,endHour: %d,endMin: %d},s3: {en: %B, startHour: %d,startMin: %d,endHour: %d,endMin: %d}}", away_delay_active, isArmed, (int)mgos_gpio_read_out(IO_O_RELAY_EN_W11), mgos_sys_config_get_sched_mode(), mgos_sys_config_get_sched_s1_en, mgos_sys_config_get_sched_s1_startHour(), mgos_sys_config_get_sched_s1_startMin(), mgos_sys_config_get_sched_s1_endHour(), mgos_sys_config_get_sched_s1_endMin(), mgos_sys_config_get_sched_s2_en, mgos_sys_config_get_sched_s2_startHour(), mgos_sys_config_get_sched_s2_startMin(),, mgos_sys_config_get_sched_s2_endHour(), mgos_sys_config_get_sched_s2_endMin(), mgos_sys_config_get_sched_s3_en, mgos_sys_config_get_sched_s3_startHour(), mgos_sys_config_get_sched_s3_startMin(), mgos_sys_config_get_sched_s3_endHour(), mgos_sys_config_get_sched_s3_endMin());

These are actually only about a third of the variables I was wanting in my shadow. It’s really tedious to manage that statement.

Mongoose does have a really convenient way to format JSON data like this, as seen below:

struct mbuf fb;
mbuf_init(&fb, 0);
struct json_out out = JSON_OUT_MBUF(&fb);

json_printf(&out, "awayDelay: %B,", away_delay_active);
json_printf(&out, "isArmed: %B,", isArmed);
json_printf(&out, "valveStatus: %d,", (int)mgos_gpio_read_out(IO_O_RELAY_EN_W11));
json_printf(&out, "schedules: {");
json_printf(&out, "mode: %d, ", mgos_sys_config_get_sched_mode());
json_printf(&out, "s1: {");
json_printf(&out, "en: %B, ", mgos_sys_config_get_sched_s1_en);
json_printf(&out, "startHour: %d,", mgos_sys_config_get_sched_s1_startHour());
json_printf(&out, "startMin: %d,", mgos_sys_config_get_sched_s1_startMin());
json_printf(&out, "endHour: %d,", mgos_sys_config_get_sched_s1_endHour());
json_printf(&out, "endMin: %d},", mgos_sys_config_get_sched_s1_endMin());
json_printf(&out, "s2: {");
json_printf(&out, "en: %B, ", mgos_sys_config_get_sched_s2_en);
json_printf(&out, "startHour: %d,", mgos_sys_config_get_sched_s2_startHour());
json_printf(&out, "startMin: %d,", mgos_sys_config_get_sched_s2_startMin());
json_printf(&out, "endHour: %d,", mgos_sys_config_get_sched_s2_endHour());
json_printf(&out, "endMin: %d},", mgos_sys_config_get_sched_s2_endMin());
json_printf(&out, "s3: {");
json_printf(&out, "en: %B, ", mgos_sys_config_get_sched_s3_en);
json_printf(&out, "startHour: %d,", mgos_sys_config_get_sched_s3_startHour());
json_printf(&out, "startMin: %d,", mgos_sys_config_get_sched_s3_startMin());
json_printf(&out, "endHour: %d,", mgos_sys_config_get_sched_s3_endHour());
json_printf(&out, "endMin: %d}", mgos_sys_config_get_sched_s3_endMin());
json_printf(&out, "}");
json_printf(&out, "}\0");


 bool response = mgos_aws_shadow_updatef(0, fb.buf);

	

Sadly, this doesn’t work because mgos_aws_shadow_updatef() already uses frozen itself, meaning that your JSON will be double-quoted and invalid.

static bool mgos_aws_shadow_updatevf(bool ext, uint64_t version,
                                     const char *state_jsonf, va_list ap) {
  bool res = false;
  if (s_shadow_state == NULL && !mgos_aws_shadow_init()) return false;
  struct mbuf data;
  mbuf_init(&data, 50);
  char token[TOKEN_BUF_SIZE];
  calc_token(s_shadow_state, token);
  struct json_out out = JSON_OUT_MBUF(&data);
  /* blast! my json formatted data is being json formatted again! */
  json_printf(&out, "{state: ");
  if (!ext) json_printf(&out, "{reported: ");
  json_vprintf(&out, state_jsonf, ap);
  if (!ext) json_printf(&out, "}");
  if (version > 0) {
    json_printf(&out, ", version: %llu", version);
  }
  json_printf(&out, ", clientToken: \"%s\"}", token);
  char *topic = get_aws_shadow_topic_name(s_shadow_state->thing_name,
                                          MGOS_AWS_SHADOW_TOPIC_UPDATE);
  LOG(LL_INFO, ("Update: %.*s", (int) MIN(200, data.len), data.buf));
  res =
      mgos_mqtt_pub(topic, data.buf, data.len, 1 /* qos */, false /* retain */);
  mbuf_free(&data);
  free(topic);
  return res;
}

bool mgos_aws_shadow_updatef(uint64_t version, const char *state_jsonf, ...) {
  if (s_shadow_state == NULL && !mgos_aws_shadow_init()) return false;
  va_list ap;
  va_start(ap, state_jsonf);
  bool res = mgos_aws_shadow_updatevf(false, version, state_jsonf, ap);
  va_end(ap);
  return res;
}

Well, no worries, right? I’ll just pass an mbuf straight in instead of feeding it through frozen first. However, from what I can tell, there isn’t an easy way to use this fancy printf-like string formatting on an mbuf this way. If there is, it’s not on the API reference page.

I suppose I could call mbuf_append() or mbuf_append_and_free() to get something similar to what I want, as long as I define my own string every time beforehand, but that’s pretty tedious. A function very similar to json_printf() for mbufs (maybe called mbuf_printf() or similar?) would be really useful for this situation.

I was kind of suspicious that this function might actually exist, but maybe it just isn’t documented on the website again. I tried to simply check the source like with the AWS documentation, but the header and source links on the API reference page 404.

Is there a simple way to do this that I’m missing- perhaps a hidden mbuf_printf() function that’s already implemented, just hiding? Or am I wrong entirely by using shadows for such large JSON objects?

Thanks for your help. I’ve been really enjoying using Mongoose OS- it just befuddles me a bit sometimes.

After formatting your data with json_printf, you can use either

bool response = mgos_aws_shadow_updatef(0, "%.*s", fb.len, fb.buf);

or

bool response = mgos_aws_shadow_update_simple(0, fb.buf);

In the second case you have to ensure the formatted string is null terminated.
mgos_aws_shadow_update_simple

mbuf.h

Awesome, I think that’s exactly what I was looking for. I’ll give it a shot in the morning. Thanks!