P1-power Bargraph textdevice

In this subforum you can show projects you have made, or you are busy with. Please create your own topic.

Moderator: leecollings

Post Reply
User avatar
RonkA
Posts: 95
Joined: Tuesday 14 June 2022 12:57
Target OS: NAS (Synology & others)
Domoticz version: 2023.2
Location: Harlingen
Contact:

P1-power Bargraph textdevice

Post by RonkA »

For my last project https://www.domoticz.com/forum/viewtopi ... 29#p317029 i made a bargraph with 3 colorzones.
I still am testing my voltage bargraph but as rron suggested i made a P1-power bargraph to see what the power usage or delivery of the house is.

So.. if there is more power produced than is used in the house it looks like this:
powerbargraph_return.PNG
powerbargraph_return.PNG (10.95 KiB) Viewed 615 times
.. and if there is more usage than is being produced in the house it looks like this:
powerbargraph_usage.PNG
powerbargraph_usage.PNG (11.02 KiB) Viewed 615 times


For the textdevice to display everything correct its vital that a monospaced font is used, in my setup this is the case but i heard from rron that this is not the case using firefox, but that is out of my control..

Max value can be set to your needs, the value set is also the minus value.
The bargraph length is adjustable to your setup, for every bar_length extra, of less the total bargraph will grow, or schrink 2 segments..

Code: Select all

-- Bargraph P1 power maker for text device -- RonkA
return {
    on = {

 -->-->-->- set correct idx number of P1_device      
        devices = {27}
       
    },
    logging = {
        level = domoticz.LOG_ERROR,
        marker = 'Bargraph',
    },
    execute = function(domoticz, triggeredItem)
-->-->-->-
        local bar_length = 13   -- number of segments in bargraph; resize as you wish
        local max_value = 5000  -- max value of Bargraph

        local text_device = domoticz.devices(339)   -- the right idx HAS to be set
        local P1_device = domoticz.devices(27)      -- set correct idx number of P1_device, also at beginning of script!!
-->-->-->- also read line 104

        local mid_value = 0   
        local initial_value = -(max_value)
        local one_segment = (max_value) / (bar_length)  -- calculate value of 1 segment  
        
        local raw_value_usage = P1_device.usage         
        local raw_value_delivered = P1_device.usageDelivered -- is a positive value
        
        local raw_value_tot = raw_value_usage - raw_value_delivered -- calculating the 'total'  
        local round_value = domoticz.utils.round(raw_value_tot, 0)  -- rounding the raw_value_tot to whole numbers
        
        -- restrain raw_value_tot to initial_value if to low   
        if raw_value_tot < initial_value then
            raw_value_tot = initial_value end
        -- restrain raw_value_tot to max_value if to high
        if raw_value_tot > max_value then
            raw_value_tot = max_value end
        
        -- helper function to format a number with a specified number of decimal places and aligned decimal points
        -- value--> the number, decimalPlaces--> number of decimalplaces, wholeNumbers--> total characters used for value including the comma.
        -- usage= formatWithDecimalPlaces(100, 2, 8) gives "  100.00"
        local function formatWithDecimalPlaces(value, decimalPlaces, wholeNumbers)
            wholeNumbers = wholeNumbers or 0 -- default value for wholeNumbers parameter is 0 if not provided
            local formatString = string.format('%%.%df', decimalPlaces)
            local formattedValue = string.format(formatString, value)
            local numDigits = string.len(formattedValue)
            local numSpaces = math.max(0, wholeNumbers - numDigits) -- adjust the number of spaces for whole numbers
            local spaces = string.rep(' ', numSpaces) -- unicode character ' ' (U+2000) for extra spaces
        return spaces .. formattedValue
        end
    
        -- calculating the segments
        local green_zone = 0
        local red_zone = 0
        local fill_zone_green = 0
        local fill_zone_red = 0
        local segment_count = 0
        
        if raw_value_tot == initial_value then
            green_zone = bar_length
            fill_zone_green = 0
            fill_zone_red = bar_length
            segment_count = fill_zone_green            
            
        elseif raw_value_tot < mid_value then
            green_zone = math.floor((mid_value - raw_value_tot) / one_segment)
            fill_zone_green = bar_length - green_zone
            fill_zone_red = bar_length
            segment_count = fill_zone_green
            
        elseif raw_value_tot == mid_value then
            fill_zone_green = bar_length
            fill_zone_red = bar_length
            red_zone = 0
            segment_count = bar_length + red_zone  
            
        elseif raw_value_tot > mid_value then
            fill_zone_green = bar_length
            fill_zone_red = math.floor((max_value - raw_value_tot) / one_segment)
            red_zone = bar_length - fill_zone_red
            segment_count = bar_length + red_zone
        end
        
        -- building the separate bar segments

        local green_bar = '<span style="color: #CCFDA9;">' .. string.rep('█', fill_zone_green) .. '</span>' ..
                          '<span style="color: green;">' .. string.rep('█', green_zone) .. '</span>'

        local red_bar = '<span style="color: Red;">' .. string.rep('█', red_zone) .. '</span>' ..
                        '<span style="color: #FDD2A9;">' .. string.rep('█', fill_zone_red) .. '</span>'
      
        -- building the bar
        -- the minus- and maximal (round_value) that is displayed correctly is from -99999W til 999999W.. should be enough..
        local bar = '<span style="font-family: Consolas; font-weight: bold; font-size: 14px;">' ..
                    initial_value .. 'W' .. '▐' .. green_bar .. '<span style="color: Gray;">█</span>' .. red_bar .. '▌' .. max_value .. 'W\n' ..
                    string.rep(' ', segment_count + 7) ..  '▲\n' .. 
                    string.rep(' ', segment_count) .. formatWithDecimalPlaces(round_value, 0, 6) .. 'W⁞' .. '</span>'
                    
        -- check if the current message is the same as the new message        
        local current_message = text_device.text
        if (current_message ~= bar) then 
            text_device.updateText(bar)  -- Update the text device
            
-->-->-->-->-- automatically clear the log of the textdevice; change the ip and port number accordingly to your setup
             domoticz.openURL('http:/127.17.0.2:80/json.htm?type=command&param=clearlightlog&idx=339')     
        else
            return -- this will stop the script and no log entry will be made
        end         
    end
}
The Domoticz log for the text device is cleared every time an update is done so no overloading the domoticz DB...
For me this works fine, i hope for others also..
Enjoy!!

EDIT, the fill colors where imho to white, they are now slightly more green and red..
SolarEdge ModbusTCP - Open Weather Map - Kaku - Synology NAS - Watermeter - ESPEasy - DS18b20
Work in progress = Life in general..
User avatar
RonkA
Posts: 95
Joined: Tuesday 14 June 2022 12:57
Target OS: NAS (Synology & others)
Domoticz version: 2023.2
Location: Harlingen
Contact:

Re: P1-power Bargraph textdevice

Post by RonkA »

I made version 2:
powerbargraph_return2.PNG
powerbargraph_return2.PNG (11.88 KiB) Viewed 539 times
powerbargraph_usage2.PNG
powerbargraph_usage2.PNG (11.83 KiB) Viewed 539 times
Now the initial_value and the max_value are inside the bargraph; this gives bargraph more space to have a better resolution.

I made it so that if the negative value gets to far to the left the pointer and value swap place so value stays viewable.
If the value is lower or higher than the outer limits the bargraph just stays max
powerbargraph_return3.PNG
powerbargraph_return3.PNG (11.6 KiB) Viewed 539 times

Code: Select all

-- Bargraph P1 power maker for text device -- RonkA
return {
    on = {

 -->-->-->- set correct idx number of P1_device      
        devices = {27}
       
    },
    logging = {
        level = domoticz.LOG_ERROR,
        marker = 'Bargraph',
    },
    execute = function(domoticz, triggeredItem)
-->-->-->-
        local bar_length = 19   -- number of segments in bargraph; resize as you wish
        local max_value = 5000  -- max value of Bargraph

        local text_device = domoticz.devices(339)   -- the right idx HAS to be set
        local P1_device = domoticz.devices(27)      -- set correct idx number of P1_device
-->-->-->- also read line 116

        local mid_value = 0     -- middle of bargraph   
        local initial_value = -(max_value)
        local one_segment = (max_value) / (bar_length)  -- calculate value of 1 segment  
        
        local raw_value_usage = P1_device.usage
        local raw_value_delivered = P1_device.usageDelivered -- is a positive value
        
        local raw_value_tot = raw_value_usage - raw_value_delivered -- calculating the 'total'  
        local round_value = domoticz.utils.round(raw_value_tot, 0)  -- rounding the raw_value_tot to whole numbers

        domoticz.log('Total segments: '.. bar_length .. ' / max_value: ' .. max_value .. ' Watt = Value per segment: ' .. domoticz.utils.round(one_segment, 1) .. ' Watt', domoticz.LOG_INFO)        
        -- restrain raw_value_tot to initial_value if to low   
        if raw_value_tot < initial_value then
            raw_value_tot = initial_value end
        -- restrain raw_value_tot to max_value if to high
        if raw_value_tot > max_value then
            raw_value_tot = max_value end
        
        -- formatWithDecimalPlaces = FWDP
        -- Helper function to format a number with a specified number of decimal places and aligned decimal points
        -- value--> het getal, decimalPlaces--> aantal achter comma, wholeNumbers--> totaal cijfers incl. punt
        -- dus FWDP(100, 2, 8) geeft "  100.00" ; FWDP(83, 0, 5) geeft "   83"
        local function FWDP(value, decimalPlaces, wholeNumbers)
            wholeNumbers = wholeNumbers or 0 -- Default value for wholeNumbers parameter is 0 if not provided
            local formatString = string.format('%%.%df', decimalPlaces)
            local formattedValue = string.format(formatString, value)
            local numDigits = string.len(formattedValue)
            local numSpaces = math.max(0, wholeNumbers - numDigits) -- Adjust the number of spaces for whole numbers
            local spaces = string.rep(' ', numSpaces) -- Unicode character ' ' (U+2000) for extra spaces
        return spaces .. formattedValue
        end 
    
        -- calculating the segments
        local green_zone = 0
        local red_zone = 0
        local fill_zone_green = 0
        local fill_zone_red = 0
        local segment_count = 0
        
        if raw_value_tot == initial_value then
            green_zone = bar_length
            fill_zone_green = 0
            fill_zone_red = bar_length
            segment_count = fill_zone_green            
            
        elseif raw_value_tot < mid_value then
            green_zone = math.floor((mid_value - raw_value_tot) / one_segment)
            fill_zone_green = bar_length - green_zone
            fill_zone_red = bar_length
            segment_count = fill_zone_green
            
        elseif raw_value_tot == mid_value then
            fill_zone_green = bar_length
            fill_zone_red = bar_length
            red_zone = 0
            segment_count = bar_length + red_zone  
            
        elseif raw_value_tot > mid_value then
            fill_zone_green = bar_length
            fill_zone_red = math.floor((max_value - raw_value_tot) / one_segment)
            red_zone = bar_length - fill_zone_red
            segment_count = bar_length + red_zone
        end
        
        -- building the separate bar segments

        local green_bar = '<span style="background-color: #CCFDA9;">' .. string.rep(' ', fill_zone_green) .. '</span>' ..
                          '<span style="background-color: green;">' .. string.rep(' ', green_zone) .. '</span>'

        local red_bar = '<span style="background-color: Red;">' .. string.rep(' ', red_zone) .. '</span>' ..
                        '<span style="background-color: #FDD2A9;">' .. string.rep(' ', fill_zone_red) .. '</span>'
      

        local nwth = string.len(max_value) + 2 -- value used to place max_value to the correct place in bargraph
        
        -- building the bar
        -- the minus- and maximal (round_value) that is displayed correctly is from -99999W til 999999W.. should be enough..
        local bar = '<span style="font-family: Consolas; font-weight: bold; font-size: 14px;">' .. '<span style="position: absolute; left: 8ch;">' .. 
                    initial_value .. 'W' .. '</span><span style="color: Silver;">▐</span>' .. green_bar .. '<span style="background-color: Silver;">0</span>' .. 
                    red_bar .. '<span style="color: Silver;">▌</span><span style="position: relative; right: ' .. nwth .. 'ch;">' .. max_value .. 'W</span>\n'

        if raw_value_tot < mid_value and segment_count <= nwth then
                    bar = bar .. string.rep(' ', segment_count + 1) ..  '▲\n' .. 
                    string.rep(' ', segment_count +1) .. '⁞' .. FWDP(round_value, 0, 6) .. 'W</span>'
        else        bar = bar .. string.rep(' ', segment_count + 1) ..  '▲\n' .. 
                    string.rep(' ', segment_count -6) .. FWDP(round_value, 0, 6) .. 'W⁞</span>'
        end
        
        -- check if the current message is the same as the new message        
        local current_message = text_device.text
        if (current_message ~= bar) then 
            -- update the text device
            text_device.updateText(bar)
            
-->-->-->-->-- automatically clear the log of the textdevice; change the ip and port number accordingly to your setup
            domoticz.openURL('http:/127.17.0.2:80/json.htm?type=command&param=clearlightlog&idx=339')     
        else
            return -- this will stop the script and no log entry will be made
        end         
    end
}
SolarEdge ModbusTCP - Open Weather Map - Kaku - Synology NAS - Watermeter - ESPEasy - DS18b20
Work in progress = Life in general..
Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest