Rule for SML Reader: iteration problem with HashMap

Tags: #<Tag:0x00007fadf8e76e88> #<Tag:0x00007fadf8e76be0>

(Thomas Binder) #3

I did that also, but thing is, even meter_map.get(1) didn’t come up with a value but NULL; I don’t get it…

nevertheless, I did some debug:

  • meter_map.get(1) = NULL
  • meter_map.keyGet() = [1, 5]
  • meter_map.values() = [Sensoren_Status_Strom, Sensoren_Leist_Strom]

I don’t see, what’s wrong here… :unamused:

(Thomas Binder) #4

quick Update, just found in an example, you can set the type of the HashMap key and value, changed the initiatialization of the HashMap to

		var HashMap<Number, String> meter_map = newLinkedHashMap(1 -> "Sensoren_Status_Strom", 5 -> "Sensoren_Leist_Strom") // "ID of value" -> "OH2 item"

But still the same error in the log:

2017-01-22 18:09:32.914 [ERROR] [ntime.internal.engine.ExecuteRuleJob] - Error during the execution of rule Stromzähler: An error occured during the script execution: Could not invoke method: org.eclipse.xtext.xbase.lib.ArrayExtensions.get(T[],int) on instance: null

If I try to debug meter_map.get(1.intValue), I even get:

2017-01-22 18:15:00.032 [ERROR] [ntime.internal.engine.ExecuteRuleJob] - Error during the execution of rule Stromzähler: An error occured during the script execution: The name '<XFeatureCallImplCustom>.get(<XMemberFeatureCallImplCustom>)' cannot be resolved to an item or type.

(Rich Koshak) #5

OK, I don’t really have time to review your rule so I’ll just present how I would do it. I’m just typing this in so there are likely some typos.

rule "Stronzahler"
    Time cron "0 */1 * * * ?"

    val String meter_payload = executeCommandLine("/usr/src/sml_server", 5000)
    val lines = meter_payload.split('\n')

    val counterStr = meter_payload.get(0).split('#').get(1)
    val consumptionStr = meter_payload.get(2).split('#').get(1)


Don’t make it more complicated than it needs to be. If may feel right to set up a map and go through all of these complicated conversions and such but:

  1. The format and contents of the string returned by your script is unlikely to change over time
  2. You are unlikely to change which Item gets which value.

So all of that added complexity does nothing to actually save you work on down the line. So just keep it simple and hard code it. If you feel you must, add a comment to explain what you are doing.

You don’t even need to split it into separate lines like I did but I think that little extra step helps make the code just a tad more self documenting.

Remember, we are not writing rules to be read and maintained by other developers. We are not writing rules that must be flexible to handle anything that you can think of to throw at it. So don’t be afraid to hard code things and use other tricks (e.g. unroll for loops, switch statements, etc.) that makes the code simpler to read. Your future self will thank you.

Finally, I think your main problem is using primitives in the map without providing the hints it needs to deal with the primitive. This might work:

val Map<Integer, String> meter_map = newLinkedHashMap(1 -> 'Sensoren_Status_Strom', 5 -> 'Sensoren_Leist_Strom')

(Thomas Binder) #6

ok, I’ll try, if I’m home - thing is, I wanted a rule also for others and other smart meters, but as you explained, it is more readable, if hard-coded! Thanks!

(Rich Koshak) #7

There is a threshold at which point it becomes worthwhile to use a more complicated but generic approach. You will have to decide for yourself if you will be hitting that threshold.

For me, if I have more than a screen’s worth of what looks like repetitions of very similar code I will look into ways to make it generic. But in a case like this I would probably move the processing of each line into a lambda but still hard code the mapping between line/parameter and Item.

Or I would name the Item such that I can create its name based on the parameter number (e.g. Sensor_Strom_1, Sensor_Strom_5 from your original code) and put them into a Group. Then your while loop becomes:

    var int i = 0
    while (i < meter_segments.size()) {
        gSensor_Strom.members.filter[s| == "Sensor_Strom_"+i].head?.sendCommand(meter_segments.get(i))
        i = i + i

The “?” will skip the rest of that line without error if the previous part is null.

This lets you bypass the Map problems entirely and shorten the rule quite a bit and maintain the flexibility to expand.

But I still maintain simple is better.

(Thomas Binder) #8

Thanks again for your help! works now as exptected!

one thing is still on my mind: is there a specific reason for Splitting the payload into lines? Just to make it more readable as you can then just have the lines to split for the delimiter?

(Rich Koshak) #9

It is as you guess, to make the code a little easier for humans to read.

I personally think it makes the code just a little more self documenting and easier to follow. It is a lot easier to think about “the second field of the third line” then it is to think about "the fifth field of this multi-line String. Writing code is an exercise in building models in your mind and anything you can do to make building those mental models easier the better off you are.

Everything is a balancing act. Yes, adding in this additional split adds complexity and length to the rule, but for me at least, the benefit it adds in making the rule easier for a human to follow more than makes up for the added two lines of code.

It really starts to go into coding style and aesthetics.

(Hallo Ween) #10

Which of the two possibilities are you using now? The first one from your first post with the improvements of rlkoshak or the new short rule from rlkoshak?

Can you post your rules-file and your items her?

I tested the small new rule-file from rlkoshak, but i get many errors with “get”-command and so on. But this functions don´t match to my (poor) programming skills - i don´t know how to fix it.

Another question:
From this rule you get the absolute counters, how do you work with this? Do you make some maths (consumption = new_counter - last_counter) or something like this to store it in a persistence? With absolute values you can´t make good charts i think?

(Thomas Binder) #11

I’m just preparing a HowTo - quick response here: I’m using the example Code of Rich like explained here:

(Thomas Binder) #12

have a look:

I hope, I didn’t get something mixed up, but it should work that way! Thanks again @rlkoshak for the main part on the rule-logic!

(Hallo Ween) #13

Do you know the max frequency to read out the power meter?

I have two ir-reader, because of my two power meters. Every reading lasts some seconds, so i only can get new values from one power meter about every 8-10 seconds.

What would be, if i install the sml_server a second time? Is there a way to do this opn the same machine, maybe with another name (sml_server2) so i could read out the two power-meters at the same time and with higher frequency?

Why is there a delay of 2-4 seconds on every reading? Is the power meter sending the data so slow or is the sml_reader parsing the given code in this time?

(Thomas Binder) #14

simple put: you must try with the lowest possible time between readings, if it’s 10seconds, this is already fast regarding, the eHZ cycles all information every 5secs I believe. The default setting in various readers is 3x per minute.
I’m not sure, why you want so fast reading cycles. Do you have some logic in mind, which needs real-time information? in that case, reading sml from an eHZ isn’t the best thing to do, you would need different meter hardware.

(Thomas Binder) #15

you can start the same sml_server more than one time. If you just copy the rule in my example in a new one, this will result in another thread of the same program. Of course, it makes no sense to have the same /dev running both instances, but with a different argument for your second reader for example you can readout both at the same time.
If the hardware allows it (Pi 3 should have no problem I guess), there should be no slow down, if you started two instances and thus getting all your two readouts in parallel.

(Hallo Ween) #16

Ok thanks, i tested it with exec in a item, not with rule.

And there was no chance to do this at the same time. Even if i set it to 1 second, i got new values all 8 seconds (4 seconds for each reader).

I´ll try this.

Higher frequency would be only useful to track some machines. Maybe i switch on my tv and i want to see how much it is consuming. But for main use, 30 seconds or 1 minute frequency would be enough.

Do you store the values into persistence? Do you store the absolute values from the counter or do you do some maths before? I want to get some charts out of rrd4j and with the absolute values, it would be useless… I think it will only work if i do it with difference between the new and the previus value?

(Thomas Binder) #17

for me, the first priority is to keep track of the meter status. Therefore I persist this value only for historic reasons.
then I persist the actual power consumption for the use in HABPanel charts, so I can easily get a feeling on how we use power throughout the day.

What I do additionally is to find a solution to Monitor specific device consumptions, like the washing machine or dishwasher. Use case here is to find out, whether the program is ready (I don’t want to open the machines to get the “ready”-voltage on the respected led). There’s a pretty sophisticated thread on this:

I don’t think, you could read 100% precise washing machine state in the overall consumption of your home. just today, I received my “Sonoff POW”, and trying figuring out, how to use it:

Migrated openhab2 IEC 62056 21 Meter Binding
(Hallo Ween) #18

I have a knx actuator with power measuring, it works great for showing states of washing machine, dryer and dishwasher.

I have a switch with 4 LEDs (each with off/white/red) in the living room and so my wife can always see, if the washing machine/dryer has finished already.

If i get the power consumption of my house only every minute, i won´t see short power users. So i think it would be the better way, to take the whole power consumption of maybe the last minute and so i can see all the power used in my house.

Persisting actual power would only be a good choice, if you get it in real time. Without that, there is too much inaccuracy.

I will try do do this with a second rule, which gave me the power difference from the last minute to another item.

(Thomas Binder) #19

the KNX acutators measuring power were too expensive for me at the time of house renovation! :wink:

hmm… real-time persistancy sounds scary! :wink: I still don’t see your use case exactly. What would you like to achieve by seeing “short power users”?
I see two ways for you (if it’s not associated with real-time actions on real-time power monitoring): adding up the overall consumption let you see changes from one minute to another (meaning the Counter was at 10.001 at 11:30 and at 10.100 at 11:31).
With that in mind you can analyse your power meter and your KNX acutator so you get the overall and the specific readings in the respective items.

(Hallo Ween) #20

real-time actions on real-time power monitoring is the right translation…

But this would only be interesting for short moments, when i plug in a new machine.

For general it would be enough to persist the power consumption for every minute.

I made a rule for this. I can see the log-info, but the item doesn´t get a new value every minute. Also there is nor error in the log…

// minütlicher Verbrauch
rule "power meter 0 Verbrauch der letzten Minute"
		Time cron "0 * * * * ?" // every 1 minutes
		logInfo("Power Meter 1 Minute", "before the rule")

               // var count_1minute = stromzaehler0_bezug.deltaSince(now.minusMinutes(1))
               // stromzaehler0_bezug_1minute.postUpdate(count_1minute)

The 2 commented lines (with //) was my first test, didn´t work too.

Do you know what´s wrong?

( ) #21

Let me know if any information is missing in the process! :wink:

@halloween I’ve been thinking about the consumption question of yours. I think it would be better or at least more flexible to persist the power readings of your devices or your house and then do further calculations in a tool like Grafana. Did you think about that?

(Hallo Ween) #22

Yes, i want to change to grafana and influx db, but first i want to change to OH2. I´m still on OH1.

Also i want to display some information, like “power consumtion last 24h hours” or something like this. So i want to do this with deltaSince. This should work with rrd4j also.