Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to "get" the int_val() #25

Open
mrWheel opened this issue Dec 12, 2019 · 8 comments
Open

How to "get" the int_val() #25

mrWheel opened this issue Dec 12, 2019 · 8 comments

Comments

@mrWheel
Copy link

mrWheel commented Dec 12, 2019

(insert code does not seem to work.. not with ticks and not with code)
Given this code example:

struct Printer {

template<typename Item>

void apply(Item &i) {

  if (i.present()) {

    Serial.print(Item::name);

    Serial.print(F(": "));

    Serial.print(i.val());

    Serial.print(Item::unit());

    Serial.println();

  }

}

};

.. how do I get the int_val()???
tried everything I can think off ..

Item::int_val

Item::int_val()

i.int_val()

i.int_val()

Thanks in advance!

@matthijskooijman
Copy link
Owner

matthijskooijman commented Dec 12, 2019

I had to dig into the code again, it has been a while and maybe I made this thing too complex :-p

Anyway, I think the problem is that the Printer::apply function is a template that is applied to all fields, but different fields will have different types. Most fields will be integer fields or String fields, but some fields use the FixedValue type defined by arduino-dsmr.

Since apply is a template function, the compiler will automatically create a copy for each type it is used on (replacing Item, the template parameter, by e.g. uint32_t or String, or FixedValue). Now, while a FixedValue object can be implicitly converted to float (or explicitly to float with val() and int with int_val()), these methods are not available on the other types such as uint32_t or String. So if you try to use them, the compiler will complain that i.int_val() does not exist when the type of i is String.

To fix this, you need to have different apply functions based on the type of i. You can do this using "template specialization", but that is a bit cumbersome. You can also use normal argument overloading (where you have multiple method definitions with the same name and different argument types, where the compiler will automatically select the overload that fits the arguments given). Since a template function is always lower priority than a non-template function, you can define non-template functions for specific types, and then still use a template function for the rest.

For example, you could do something like:

struct Printer {
  // Default implementation, just pass i to print
  template<typename Item>
  void apply(Item &i) {
    if (i.present())
    {
      Serial.print(i);
    }
  }

  // For FixedValue, get underlying value
  void apply(FixedValue i) {
    // Print the int value (in thousands)
    Serial.print(i.int_val());
    // Or get the float value and print with 3 decimals
    Serial.print(i.val(), 3);
  }
};

I have not tested this, but I expect this to work. You can do the same thing for String, e.g. if you want to add quotes around the string for example (if so, be sure to use String &i as the argument, using & to create a reference to prevent unneeded copying of the string).

From your e-mail, I gather you're building a JSON-printer. Nice idea! If you get it to work, would you share the result? That would be awesome to include as an example. I should probably also properly document how this printer thing works, and using a JSON printer would be a great example to illustrate that.

@matthijskooijman
Copy link
Owner

W00ps, I accidentally used apply(float), but that should be apply(FixedValue) instead. I've updated my previous comment.

@michelwilson
Copy link

So I seem to be running into the same issue, and I don't have enough C++ knowledge to understand what is going on here. First, i tried the example in your reply, but that doesn't seem to be correct, as here I don't have access to the name() function (which I kinda need, otherwise I don't know which value I'm accessing). Also, the compiler doesn't seem to pick up my non-template function for that type, I still get errors about no matching function call to blabla(dsmr::FixedValue&). Defining apply(FixedValue &i) also doesn't work. I think it should be something like apply(FixedField &i), but then the compiler wants me to specify an argument list for a class template. No idea what that should be... Any ideas?

@matthijskooijman
Copy link
Owner

I think my previous suggestion was not actually working because there is no single FixedField class, but it is a template that is instantiated once for every particular field (which is why it knows the name in the first place). I was probably thinking about FixedValue, which is a single class, but that's not what's passed to the printer (also, it doesn't know its name anymore).

So I think it should be something like this (again untested, just did this quickly after looking at he code a bit more):

struct Printer {
  // Default implementation, just pass i to print
  template<typename Item>
  void apply(Item &i) {
    if (i.present())
    {
      Serial.print(Item::name);
      Serial.print(" ");
      Serial.println(i);
    }
  }

  // For FixedField, get underlying value
  template <typename T, const char *_unit, const char *_int_unit>
  void apply(const FixedField<T, _unit, _int_unit>& i) {
    if (((const T&)i).present()) {
      Serial.print(T::name);
      Serial.print(" ");
      // Print the int value (in thousands)
      Serial.print(((const T&)i).val().int_val());
      // Or get the float value and print with 3 decimals
      Serial.print(((const T&)i).val().val(), 3);
    }
  }
};

Note that:

  • The actual type of the argument is not FixedField<...> at all, but it is the field-specific subclass of FixedField, as declared by DEFINE_FIELD. However, to have an apply function that will catch all fields that derive from FixedField, this needs a const reference argument (You cannot apply an argument to a non-reference parameter of a superclass type, but you can do this for a (const) reference parameter). One caveat is that I'm not 100% sure whether the compiler will prefer this superclass-reference argument over the more generic Item version of apply. One alternative approach here would be to use std::enable_if and std::base_of to have a generic Item version that only applies to subclasses of FixedField.
  • The FixedField class itself has no access to the name or value of the field, because that's stored in the subclass. But FixedField does know the type of the subclass (this is the T template argument), so (const T&)i actually refers to the subclass (not 100% sure if this cast is allowed, but I think you can cast from superclass to subclass if you're sure that it really is an instance of that subclass).
  • This means that ((const &T&)i).val() refers to the FixedValue returned by this val() method and ((const &T&)i).val().val() refers to the float returned by this val() method.

Let me know if this helps (and feel free to ask again if it doesn't work, I can probably find time to look a bit deeper and test code if needed).

I wonder if the datastructure could/should be refactored to make this stuff easier somehow...

@mrWheel
Copy link
Author

mrWheel commented Jan 24, 2021

I created a push request (#32) that has no conflict with the rest of the code and adds an example for this.

@michelwilson
Copy link

@mrWheel thanks!! That example made my compiler happy!
@matthijskooijman unfortunately, your example doesn't work, the compiler still seems to only look at the first function, I get the same error. I'm not of much help here, my knowledge of C++ templating is non-existent, and I must say, it's pretty cryptic, coming from a Java and C world ;)

Unfortunately, I can't test if it actually works, because it appears that my P1 meter is unwilling to provide enough power for my esp8266. I'll rig up something with an external supply the coming days, to see if it works that way.

@mrWheel
Copy link
Author

mrWheel commented Jan 24, 2021

@mrWheel thanks!! That example made my compiler happy!

Look here!

@michelwilson
Copy link

@mrWheel thanks!! That example made my compiler happy!

Look here!

That is very very fancy, thanks. More than I need I think, but contains lots of good info and inspiration. FYI I'm trying to build this into a Homie sensor node, to feed the data into my home automation setup, see here if you're not familiar yet with Homie :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants