Are you able to get to a shell (aka command line) on your HA system? If so, just execute the command python3. If not, then use any computer that has python installed, or install it. If you need help with the details I’d suggest Googling. That’s a bit beyond the scope of this forum.
not sure…duh.my Ha system is Hassio or Hassos, so no other software on that, maybe an @Frenck add-on? Will check .
Up to now I always edit in the Python script itself, as it updates immediately. Which is one of the bigger advantages using Python
making slight progress:
changed the ---- into ==== which makes a lot of difference in the spacing. took out the first line '*{:=^30}'.format(' Sensors '),
which seems a bit superfluous.
your new way of rounding had unexpected effects, the values seemed unstable, and often changed into many decimals after the comma. changed into this now makes it stable as a rock:
try:
power = float(state.state)
except:
continue
if power > 0:
total_power = round(total_power + power,2)
count = count + 1
and makes my other sensor:
hass.states.set('sensor.total_sensors',total_power, {
'unit_of_measurement': 'Watt',
'friendly_name': '{} sensors'.format(count),
'count': count
})
stable as well.
also found an easier way for the sensor names, no title()
necessary anymore:
sensor_list.append('#{:21}: {:>7.2f}'.format(
state.name.replace('actueel','').replace('_',' '),
power))
thing I noticed is that even states without a decimal now show .00
dont think I had that before, but I can appreciate this being necessary for the alignment?
Still havent found out why the sensor lines and the Total line won’t align with the '*{:=^20} : {:=^7}'.format(' Sensor ', 'Power ')
line. No matter what i fill in the {}, the positions remains the same…
some further colorization implemented:
colorCode = '%' if power < 20 else \
'&' if power < 100 else \
'+' if power < 300 else \
'#' if power < 500 else \
'=' if power < 1500 else \
'!' if power < 2000 else \
''
colorCodeTotal = '%' if total_power < 200 else \
'&' if total_power < 500 else \
'+' if total_power < 1000 else \
'#' if total_power < 1500 else \
'=' if total_power < 2000 else \
'!' if total_power < 3000 else \
'*'
and
sensor = colorCode +'{:22}:'.format(state.name[:-8]) + '{:>7}\n'.format(state.state)
sensor_list.append(sensor)
had a little go understanding your new summary style, so forgive me for changing. This is still very much so a learning process…
I kind of like the formatting placeholders in the summary, and the content or variable in the .format()
.
summary = '\n'.join([
'*{:=^20} : {:=^7}', # = 30
*sorted(sensor_list),
'*{:=^30}', # = 30
'/ {:^21}: {:^7}', # = 30
colorCodeTotal +'{:^21}: {:>7.2f}', # = 30
'*' + '='*30 # = 30
]).format(' Sensor ','Power ',
' Summary ',
'Total Sensors consuming power',count,
'Total consumed Power',total_power,
total_power)
Colors are working fine now, so cool. Only thing is, the sorted(sensor_list) now sorts per color unexpected (for me) and have to see if that can be changed to sort alphabetically.
optimally I would like to create the list colorCode =['*','!','+','=','%','$','#']
and have the comparison check for values power <50, 100, 300, 500, 1500, 2000, or else (when >2000) turn ‘*’.
Could(Should) I do that in another way than fully written out like now?
btw, what does the *
do in front of the *sorted(sensor_list)
?
thought it to be a remnant of my colorizations, so took it out, but then the script stops and errors with: TypeError: sequence item 1: expected str instance, list found
.
Could there be a mixup in the code with the * being used for several things?
Most of your recent questions have to do with how to do things in Python. You really should take some time and find a nice online Python tutorial. You’d be able to figure a lot of this out yourself instead of all this trial and error.
*iterable
means expand the iterable. So *['a','b','c']
is 'a', 'b', 'c'
. I’m using it here to expand sensor_list into individual items that then become part of the bigger list.
You can give the function sorted a key function that can change what it sorts. So you could change *sorted(sensor_list)
to *sorted(sensor_list, key=lambda x: x[1:])
. So instead of sorting by the whole strings, it will sort using the strings minus their first characters. Again, this depends on whether or not the sandboxed python_scripts environment allows the use of the lambda keyword.
why thank you indeed, that does it. This has become a nice little script, glad to share the final (for now) version… really appreciated.
##########################################################################################
# map_total_sensors.py
# reading all sensors.*_actueel using power (>0), listing and counting them,
# sorted alphabetically, and calculate summed power consumption
# colorcoded by power usage
# by @Mariusthvdb and big hand Phil, @pnbruckner
# https://community.home-assistant.io/t/template-to-add-values-of-all-sensors-with-certain-name-ending/64488
# august 21 2018
##########################################################################################
sensor_list = []
total_power = 0
count = 0
for entity_id in hass.states.entity_ids('sensor'):
if entity_id.endswith('_actueel') and not 'sensors' in entity_id:
state = hass.states.get(entity_id)
try:
power = float(state.state)
except:
continue
if power > 0:
total_power = round(total_power + power,2)
count = count + 1
colorCode = '%' if power < 20 else \
'$' if power < 100 else \
'+' if power < 300 else \
'#' if power < 500 else \
'=' if power < 1500 else \
'!' if power < 2000 else \
''
colorCodeTotal = '%' if total_power < 200 else \
'$' if total_power < 500 else \
'+' if total_power < 1000 else \
'#' if total_power < 1500 else \
'=' if total_power < 2000 else \
'!' if total_power < 3000 else \
'*'
sensor = colorCode +'{:22}:'.format(state.name[:-8]) + \
'{:>7}\n'.format(state.state)
sensor_list.append(sensor)
summary = '\n'.join([
'*{:=^20} : {:=^7}',
*sorted(sensor_list,key=lambda x: x[1:]),
'*{:=^30}',
'/ {:^21}: {:^7}',
colorCodeTotal +'{:^21}: {:>7.2f}',
'*' + '='*30
]).format(' Sensor ','Power ',
' Summary ',
'Total Sensors consuming power',count,
'Total consumed Power',total_power,
total_power)
hass.states.set('sensor.map_total_sensors', '', {
'custom_ui_state_card': 'state-card-value_only',
'text': summary
})
hass.states.set('sensor.total_sensors',total_power, {
'unit_of_measurement': 'Watt',
'friendly_name': '{} sensors'.format(count),
'count': count
})
##########################################################################################
# map_total_sensors.py
##########################################################################################
##########################################################################################
# Codes for text_colors declared in
# Custom card: /custom_ui/state-card-value_only.html
##########################################################################################
# case "*": return "bold";
# case "/": return "italic";
# case "!": return "red";
# case "+": return "green";
# case "=": return "yellow";
# case "%": return "grey";
# case "$": return "brown";
# case "#": return "blue";
# default: return "normal";
##########################################################################################
##########################################################################################
# lessons learned:
##########################################################################################
#Try. The else doesn’t go with the if, it goes with the try. Notice that it is at the same
#indentation level as try and except. In a try statement, if an exception occurs
#the except clause is executed.
#But if no exception occurs, then the else clause is executed (if there is one.)
#So, in this case, if the conversion to int works, then the else clause it executed,
#which increments the count.
#*iterable means expand the iterable. So *['a','b','c'] is 'a', 'b', 'c'. Used here to
#expand sensor_list into individual items that then become part of the bigger list.
#sorted. You can give the function sorted a key function that can change what it sorts.
#So you could change *sorted(sensor_list) to *sorted(sensor_list, key=lambda x: x[1:]).
#Instead of sorting by the whole strings, it will sort using the strings minus their
#first characters.
##########################################################################################
# Older snippets
##########################################################################################
# logger.info('entity_id = {}, state = {}'.format(entity_id, state.state))
# sensor_id = '{}'.format(entity_id[7:-8].replace('_',' ').title())
# sensor_power = '\t{}'.format(state.state)
# sensor = '#{} :'.format(sensor_id) + ' {}\n'.format(sensor_power)
# sensor_list.append(sensor)
## sensor_list.append('%{:21}:{:>7.2f}'.format(
# state.name.replace('actueel','').replace('_',' '),
# power))
# sensor_list.append(colorCode +'{:21}: {:>7.2f}'.format(
# state.name.replace('actueel','').replace('_',' '),
# power))
#sensor_map = '\n'.join(sensor_list)
#summary = '*{:=^30}\n' \
# '$---------- Sensor : Power ----------\n' \
# '{}\n' \
# '*=============== Sumary ==============\n' \
# ' Sensors consuming power: {}\n' \
# ' Total consumed Power : {}\n' \
# '*====================================\n' \
# .format(' Sensors ',
# sensor_map,
# count,
# total_power)
##########################################################################################
# map_total_sensors.py
##########################################################################################
@Mariusthvdb I’ll echo what pnbruckner said. You should look at a python tutorial. You’ve got a good brain for figuring this stuff out
…
thanks, i guess;-) ive had @petro suggest likewise…
the thing is, I started with HA to make life easier…
then found some very nice people that help me doing things I never imagined doing before, and now endup in need of extra tutoring
Believe me, i want to, and try to grab each opportunity learning things, as methodically as i think it should be done. Not very easy though with limited time and other chores. No such thing as diving in and learning on the job
Addictive it is though, so i’m in for another little hour community browsing now, trying to help others, and seek some assistance myself…
btw @nigell did you have the opportunity to see why the device_trackers aren’t shown in the orphans script?
No I haven’t looked at that, send me a PM to remind me, and I will try to look at this weekend
back for a small thing…
using this automation value_template for another sensor, showing me the last run automations. Skipping the really frequent ones, because without that, it halts the system… (activate_map_sensors_actueel is one of the culprits, as I feared…):
value_template: >
{% set skip_list = ['automation_ran', 'count_warnings', 'count_errors',
'activate_map_sensors_actueel'] %}
{{ trigger.event.data.entity_id.startswith('automation.') and
trigger.event.data.entity_id.split('.')[1] not in skip_list and
'old_state' in trigger.event.data and 'new_state' in trigger.event.data }}
which works fine.
was trying to shorten the template some further doing this:
value_template: >
{% set skip_list = ['automation_ran', 'count_warnings', 'count_errors',
'activate_map_sensors_actueel'] %}
{{ trigger.event.data.entity_id.startswith('automation.') and
trigger.event.data.entity_id.split('.')[1] not in skip_list and
['old_state','new_state'] in trigger.event.data }}
but this errors out stating it can run the list (not exactly but changed it back again so cant reproduce now)
would there be another way of compressing this:
and
{{ 'old_state' in trigger.event.data and 'new_state' in trigger.event.data }}
thx
There’s no way to compress that because you are looking for individual items inside the keys of trigger.event.data. When you do someting like [‘x’,‘y’] in somelist, it will look for the entire object [‘x’,‘y’], not both objects individually.
Ok cool. Thanks .
for reference here’s the complete setup including adapted state-card-value_only.html. Save this card in /config/custom_ui and call it in frontend.yaml:
extra_html_url:
- /local/custom_ui/state-card-value_only.html
copy below code and save as state-card-value_only.html
<!--
https://github.com/home-assistant/home-assistant-polymer/blob/master/src/state-summary/state-card-display.html
https://github.com/home-assistant/home-assistant-polymer/blob/master/src/components/entity/state-badge.html
https://github.com/PolymerElements/paper-styles/blob/master/color.html
# (expanded from original with extra color codes and corrected cases bold/italic)
# paper-brown-500: #795548 and google-grey-500: #9e9e9e, blue, darkbrown, deepbrown, purple
# by @Mariusthvdb
-->
<dom-module id="state-card-value_only">
<template>
<style is="custom-style" include="iron-flex iron-flex-alignment"></style>
<style>
.bold {
@apply(--paper-font-body1);
color: var(--primary-text-color);
font-weight: bold;
margin-left: 8px;
text-align: left;
line-height: 20px;
}
.italic {
@apply(--paper-font-body1);
color: var(--primary-text-color);
font-style: italic;
margin-left: 8px;
text-align: left;
line-height: 20px;
}
.red {
@apply(--paper-font-body1);
color: var(--google-red-500);
margin-left: 8px;
text-align: left;
line-height: 20px;
}
.green {
@apply(--paper-font-body1);
color: var(--google-green-500);
margin-left: 8px;
text-align: left;
line-height: 20px;
}
.yellow {
@apply(--paper-font-body1);
color: var(--google-yellow-500);
margin-left: 8px;
text-align: left;
line-height: 20px;
}
.grey {
@apply(--paper-font-body1);
color: #9e9e9e;
margin-left: 8px;
text-align: left;
line-height: 20px;
}
.brown {
@apply(--paper-font-body1);
color: #795548;
margin-left: 8px;
text-align: left;
line-height: 20px;
}
.blue {
@apply(--paper-font-body1);
color: var(--google-blue-500);
margin-left: 8px;
text-align: left;
line-height: 20px;
}
.darkbrown {
@apply(--paper-font-body1);
color: #500000;
margin-left: 8px;
text-align: left;
line-height: 20px;
}
.deepbrown {
@apply(--paper-font-body1);
color: #2d0000;
margin-left: 8px;
text-align: left;
line-height: 20px;
}
.purple {
@apply(--paper-font-body1);
color: #2d214b;
margin-left: 8px;
text-align: left;
line-height: 20px;
}
.normal {
@apply(--paper-font-body1);
color: #4b4b4b;
margin-left: 8px;
text-align: left;
line-height: 20px;
}
</style>
<template is="dom-repeat" items="[[computeStateDisplay(stateObj)]]">
<div class$="[[computeClass(item)]]">[[computeItem(item)]]</div>
</template>
</template>
</dom-module>
<script>
Polymer({
is: 'state-card-value_only',
properties: {
hass: {
type: Object,
},
stateObj: {
type: Object,
},
},
computeStateDisplay: function (stateObj) {
var text = stateObj.attributes.text;
if (text == null) { text = stateObj.state };
return text.split("\n");
},
computeItem: function (item) {
var value = item.trim();
switch(value.substring(0,1)) {
case "*":
case "/":
case "!":
case "+":
case "=":
case "%":
case "$":
case "#":
case "@":
case "^":
case "&":
return value.substring(1);
default:
return value;
}
},
computeClass: function (item) {
switch(item.trim().substring(0,1)) {
case "*": return "bold";
case "/": return "italic";
case "!": return "red";
case "+": return "green";
case "=": return "yellow";
case "%": return "grey";
case "$": return "brown";
case "#": return "blue";
case "@": return "darkbrown";
case "^": return "deepbrown";
case "&": return "purple";
default: return "normal";
}
},
});
</script>
below automation to run the python_script:
automation:
- alias: 'Activate Map sensors actueel'
id: 'Activate Map sensors actueel'
#hide_entity: True
initial_state: 'off'
trigger:
platform: event
event_type: state_changed
event_data:
domain: sensor
condition:
condition: template
value_template: >
{{ trigger.event.data.entity_id.endswith('actueel')}}
action:
service: python_script.map_sensors_actueel
the group to show it in the frontend
group:
map_sensors_actueel:
name: Map sensors actueel
icon: mdi:power
entities:
- sensor.map_sensors_actueel
- automation.activate_map_sensors_actueel
Ive set the automation initial_state: 'off'
and placed the automation in the same group, so it doesnt start up by default (seems to take lots of processing effort for the Pi) and switching on/off is always quickly available.
updated and renamed python_script map_sensors_actueel:
##########################################################################################
# map_sensors_actueel.py
# reading all sensors.*_actueel using power (>0), listing and counting them,
# sorted alphabetically, and calculate summed power consumption
# colorcoded by power usage
# by @Mariusthvdb and big hand Phil, @pnbruckner
# https://community.home-assistant.io/t/template-to-add-values-of-all-sensors-with-certain-name-ending/64488
# august 21 2018
##########################################################################################
sensor_list = []
total_power = 0
count = 0
for entity_id in hass.states.entity_ids('sensor'):
if entity_id.endswith('_actueel') and not 'sensors' in entity_id:
state = hass.states.get(entity_id)
try:
power = float(state.state)
except:
continue
if power > 0:
total_power = round(total_power + power,2)
count = count + 1
colorCode = '%' if power < 20 else \
'=' if power < 100 else \
'+' if power < 200 else \
'#' if power < 500 else \
'$' if power < 1000 else \
'@' if power < 1500 else \
'^' if power < 2000 else \
'!' if power < 2250 else \
'&' if power < 2500 else \
''
colorCodeTotal = '%' if total_power < 200 else \
'=' if total_power < 500 else \
'+' if total_power < 1000 else \
'#' if total_power < 1500 else \
'$' if total_power < 2000 else \
'@' if total_power < 3000 else \
'^' if total_power < 4000 else \
'!' if total_power < 5000 else \
'&' if total_power < 6000 else \
'*'
sensor = colorCode +'{:22}:'.format(state.name[:-8]) + \
'{:>7}\n'.format(state.state)
sensor_list.append(sensor)
summary = '\n'.join([
'*=== Sensor ===== Power =======',
*sorted(sensor_list,key=lambda x: x[1:]),
'*========== Summary ==========',
'/Total Sensors consuming power: {}',
colorCodeTotal +'Total consumed Power: {}',
'*' +'='*28
]).format(count,
total_power,
total_power)
hass.states.set('sensor.map_sensors_actueel', '', {
'custom_ui_state_card': 'state-card-value_only',
'text': summary
})
hass.states.set('sensor.total_sensors',total_power, {
'unit_of_measurement': 'Watt',
'friendly_name': '{} sensors'.format(count),
'count': count
})
##########################################################################################
# map_sensors_actueel.py
##########################################################################################
##########################################################################################
# Codes for text_colors declared in
# Custom card: /custom_ui/state-card-value_only.html (expanded from original with extra
# color codes and corrected cases bold/italic)
##########################################################################################
# case "*": return "bold";
# case "/": return "italic";
# case "!": return "red";
# case "+": return "green";
# case "=": return "yellow";
# case "%": return "grey";
# case "$": return "brown";
# case "#": return "blue";
# case "@": return "darkbrown";
# case "^": return "deepbrown";
# case "&": return "purple";
# default: return "normal";
##########################################################################################
##########################################################################################
# lessons learned:
##########################################################################################
#Try. The else doesn’t go with the if, it goes with the try. Notice that it is at the same
#indentation level as try and except. In a try statement, if an exception occurs
#the except clause is executed.
#But if no exception occurs, then the else clause is executed (if there is one.)
#So, in this case, if the conversion to int works, then the else clause it executed,
#which increments the count.
#*iterable means expand the iterable. So *['a','b','c'] is 'a', 'b', 'c'. Used here to
#expand sensor_list into individual items that then become part of the bigger list.
#sorted. You can give the function sorted a key function that can change what it sorts.
#So you could change *sorted(sensor_list) to *sorted(sensor_list, key=lambda x: x[1:]).
#Instead of sorting by the whole strings, it will sort using the strings minus their
#first characters.
##########################################################################################
# Older snippets
##########################################################################################
# logger.info('entity_id = {}, state = {}'.format(entity_id, state.state))
# sensor_id = '{}'.format(entity_id[7:-8].replace('_',' ').title())
# sensor_power = '\t{}'.format(state.state)
# sensor = '#{} :'.format(sensor_id) + ' {}\n'.format(sensor_power)
# sensor_list.append(sensor)
## sensor_list.append('%{:21}:{:>7.2f}'.format(
# state.name.replace('actueel','').replace('_',' '),
# power))
# sensor_list.append(colorCode +'{:21}: {:>7.2f}'.format(
# state.name.replace('actueel','').replace('_',' '),
# power))
#sensor_map = '\n'.join(sensor_list)
#summary = '*{:=^30}\n' \
# '$---------- Sensor : Power ----------\n' \
# '{}\n' \
# '*=============== Sumary ==============\n' \
# ' Sensors consuming power: {}\n' \
# ' Total consumed Power : {}\n' \
# '*====================================\n' \
# .format(' Sensors ',
# sensor_map,
# count,
# total_power)
# complete version @pnbruckner
#sensor_list = []
#total_power = 0
#count = 0
#for entity_id in hass.states.entity_ids('sensor'):
# if entity_id.endswith('_actueel') and 'sensors' not in entity_id:
# state = hass.states.get(entity_id)
# try:
# power = round(float(state.state), 2)
# except:
# continue
# if power > 0:
# total_power = total_power + power
# count = count + 1
# sensor_list.append('#{:21}: {:>7.2f}'.format(
# state.object_id.replace('_actueel','').replace('_',' ').title(),
# power))
#summary = '\n'.join([
# '*{:=^30}'.format(' Sensors '),
# '${:-^20} : {:-^7}'.format(' Sensor ', 'Power '),
# *sensor_list,
# '*{:=^30}'.format(' Summary '),
# '$ Total: # {:<4} Power : {:>7.2f}'.format(count, total_power),
# '*' + '='*30])
#
#hass.states.set('sensor.map_total_sensors', '', {
# 'custom_ui_state_card': 'state-card-value_only',
# 'text': summary})
##########################################################################################
# map_sensors_actueel.py
##########################################################################################
ok, since its my BD
I have another request for you @pnbruckner, @petro and @NigelL:
this
colorCode = '%' if power < 20 else \
'=' if power < 100 else \
'+' if power < 200 else \
'#' if power < 500 else \
'$' if power < 1000 else \
'@' if power < 1500 else \
'^' if power < 2000 else \
'!' if power < 2250 else \
'&' if power < 2500 else \
''
colorCodeTotal = '%' if total_power < 200 else \
'=' if total_power < 500 else \
'+' if total_power < 1000 else \
'#' if total_power < 1500 else \
'$' if total_power < 2000 else \
'@' if total_power < 3000 else \
'^' if total_power < 4000 else \
'!' if total_power < 5000 else \
'&' if total_power < 6000 else \
'*'
looks to Un-Pythonesque .
shouldn’t this be coded like:
colorCodeTotal = ['%', '=' , '+' , '#' ,'$','@','^' , '!', '&', '*']
and some logic selecting the colorCodeTotal[logic] base on the value_test ?
I got myself a Python course as a present, but this is a bit further in the semester I fear. Hoped you would point me in the right direction a bit sooner …
Maybe the List is too complicated for this use-case, and if so, please check if this could be simplified otherwise.
thx!
Happy birthday!
Given the sequence of thresholds is not linear I think what you have might be the best you can do. If a linear sequence was ok, then you could index an array like you created with a simple equation. You’d have to make sure of two things, though. First, you’d want an if statement to return some value if the power value would cause the equation to index outside the range of the array. Second, you’d want to make sure the result of the equation was in int before using it to index into the array.
If a linear sequence doesn’t do what you want, then probably a logarithmic one would. However, since I think you want to run this in the limited python_script environment, I’m not sure you’ll be able to use that.
understood, as a matter of fact, i use that in one of my other scripts (much helped by @petro) to select a theme:
themelist = groups_theme[idx].split('|')
if len(themelist) > 1:
try:
theme = themelist[groups_count[idx]]
except IndexError:
theme = 'green_badge' # error badge)
else:
theme = 'black_badge' # this will be the default theme
the fact that I now need a non-lineair sequence got me in trouble here… glad you confirm I didn’t solve it too clumsily. you’re invited for a Piece of cake
Hey @Mariusthvdb. Hope you had a great birthday.
I can come up with other ways to select the colour based on power, but they wouldn’t qualify as ‘better’. Sometimes the hardest part of the job is knowing when to not do it.
Thanks Guys, for your wishes and for your advice. I’ll settle it then.
Cool.
I know I’m late to the party here, but newer versions of Jinja provide a method to retain variable scope outside of a loop. I used this to make template sensors for my total network client bandwidth, as in:
{% set ns = namespace(rx=0.0, tx=0.0) %}
{% for state in states.sensor %}
{% if state.entity_id.endswith('tx') and state.state|float>0 %}
{% set ns.tx = ns.tx + state.state|float %}
{% elif state.entity_id.endswith('rx') and state.state|float>0 %}
{% set ns.rx = ns.rx + state.state|float %}
{% endif %}
{% endfor %}
RX Total: {{ ns.rx }}
TX Total: {{ ns.tx }}