Feature request: 3-phase voltage device

Use this forum to discuss possible implementation of a new feature before opening a ticket.
A developer shall edit the topic title with "[xxx]" where xxx is the id of the accompanying tracker id.
Duplicate posts about the same id. +1 posts are not allowed.

Moderators: leecollings, remb0

Post Reply
highvoltage
Posts: 3
Joined: Friday 27 May 2016 17:22
Target OS: Linux
Domoticz version: 2025.2
Location: Netherlands
Contact:

Feature request: 3-phase voltage device

Post by highvoltage »

Hi all,

I would like to request a new dummy device type in Domoticz for measuring 3-phase voltage.

Currently, we have a dummy device for a 3-phase current (ampere) meter, which works very well. It would be great to have a similar option for voltage, allowing users to input and display voltage values for all three phases.

Thanks for considering!
User avatar
RonkA
Posts: 128
Joined: Tuesday 14 June 2022 12:57
Target OS: NAS (Synology & others)
Domoticz version: 2025.1
Location: Harlingen
Contact:

Re: Feature request: 3-phase voltage device

Post by RonkA »

Because i also wanted to see what the mains-voltages was doing i made this dzVents-script a view jears ago that shows the voltages provided by the P1-port of my mains-meter:
netspanning.PNG
netspanning.PNG (20.33 KiB) Viewed 67 times
Spoiler: show

Code: Select all

-- Voltage Bargraph maker for text device -- RonkA

return {
    on = {
        timer = {'at 23:59'},

        devices = {27} -->-->-->- set correct idx number of P1_device   
        
    },

    data = {
        min_voltage_L1 = { initial = 229.9 }, -- Initialize the min_voltage_L1 variable; for 120v mains set to 119.9
        min_voltage_L2 = { initial = 229.9 }, -- Initialize the min_voltage_L2 variable; for 120v mains set to 119.9
        min_voltage_L3 = { initial = 229.9 }, -- Initialize the min_voltage_L3 variable; for 120v mains set to 119.9
        max_voltage_L1 = { initial = 230 }, -- Initialize the max_voltage_L1 variable; for 120v mains set to 120.1
        max_voltage_L2 = { initial = 230 }, -- Initialize the max_voltage_L2 variable; for 120v mains set to 120.1
        max_voltage_L3 = { initial = 230 }, -- Initialize the max_voltage_L3 variable; for 120v mains set to 120.1
    },  

--[[    logging = {
        level = domoticz.LOG_ERROR,
        marker = 'Bargraph',
    },
]]
    execute = function(domoticz, device)
-->-->-->- values are set for 230 volt mains with a 10% tolerance according to NEN-EN 50160
        -- for 120 volt mains with a 5% tolerance (ANSI standard) adjust values accordingly 
-- domoticz.log('Bargraph - Net - Voltages ' .. device.name, domoticz.LOG_INFO)       
        local padding = 7               -- number of invisible blocks to allign the 2nd and 3rd bargraph in the textdevice          
        local bar_length = 17           -- number of segments in bargraph       - for 120 volts mains 
        local low_value = 207           -- begin value of the low red zone      - 114
        local low_red_value = 212       -- value to start the low red zone      - 116
        local low_yellow_value = 220    -- value to start the low yellow zone   - 118
        local mid_value = 230           -- middle of green zone                 - 120
        local high_yellow_value = 240   -- value to start the high yellow zone  - 122
        local high_red_value = 248      -- value to start the high red zone     - 124
        local high_value = 253          -- end value of the high red zone       - 126

        -- fictional values if less than 3 phase are used.. do not remove!       
        local voltageL2 = 231
        local voltageL3 = 231        

-->-->-->- for 1 phase metering only use voltageL1; voltageL2 and voltageL3 have to be commented out! (set -- in front of local..)
        -- for 2 phase metering only use voltageL1 and voltageL2; voltageL3 has to be commented out!
        -- set correct idx number of voltage device(s) 
        local voltageL1 = domoticz.utils.round(domoticz.devices(28).voltage,1)
        local voltageL2 = domoticz.utils.round(domoticz.devices(29).voltage,1)
        local voltageL3 = domoticz.utils.round(domoticz.devices(30).voltage,1)
        


-->-->-->- idx number of the textdevice; change accordingly
        local text_device = domoticz.devices(337)  
        
        -- initialize min and max voltage values
        local voltageL1_min = domoticz.data.min_voltage_L1
        local voltageL2_min = domoticz.data.min_voltage_L2
        local voltageL3_min = domoticz.data.min_voltage_L3
        local voltageL1_max = domoticz.data.max_voltage_L1
        local voltageL2_max = domoticz.data.max_voltage_L2
        local voltageL3_max = domoticz.data.max_voltage_L3

        -- store value when voltage is lower or higer than the stored min or max voltage
        if voltageL1 < mid_value and voltageL1 < voltageL1_min then
            domoticz.data.min_voltage_L1 = voltageL1
        end
        if voltageL2 < mid_value and voltageL2 < voltageL2_min then
            domoticz.data.min_voltage_L2 = voltageL2
        end
        if voltageL3 < mid_value and voltageL3 < voltageL3_min then
            domoticz.data.min_voltage_L3 = voltageL3
        end
        if voltageL1 > mid_value and voltageL1 > voltageL1_max then
            domoticz.data.max_voltage_L1 = voltageL1
        end
        if voltageL2 > mid_value and voltageL2 > voltageL2_max then
            domoticz.data.max_voltage_L2 = voltageL2
        end
        if voltageL3 > mid_value and voltageL3 > voltageL3_max then
            domoticz.data.max_voltage_L3 = voltageL3
        end
        
        -- check if it's nighttime (23:59) to reset bargraph variables
        if (device.isTimer) then
            domoticz.log('Reset Bargraph Voltage Variables', domoticz.LOG_INFO)
            domoticz.data.min_voltage_L1 = 229.9
            domoticz.data.min_voltage_L2 = 229.9
            domoticz.data.min_voltage_L3 = 229.9
            domoticz.data.max_voltage_L1 = 230
            domoticz.data.max_voltage_L2 = 230
            domoticz.data.max_voltage_L3 = 230
	        return 
		end
		
        -- function to take (value) and make half a bargraph in the correct manner.
        --if value is lower than mid_value bargraph from right to left and if value is higher than mid_value from left to right 
        local function createBargraph(value)
        local one_segment = (high_value - low_value) / (2 * bar_length)
    
        local low_green_zone = math.abs(math.floor((low_yellow_value - mid_value) / one_segment))
        local low_yellow_zone = math.abs(math.floor((low_red_value - mid_value) / one_segment))
        local low_red_zone = bar_length
    
        local high_green_zone = math.floor((high_yellow_value - mid_value) / one_segment)
        local high_yellow_zone = math.floor((high_red_value - mid_value) / one_segment)
        local high_red_zone = bar_length
    
        local text_value = value
        
        -- stop value to get lower or higher than outer values of bargraph     
        if value < low_value then 
            value = low_value         
        elseif value > high_value then 
            value = high_value        
        end
        
        -- determine if value is lower or higer than mid_value
        local segment_count = math.abs(math.floor((value - mid_value) / one_segment))
        local is_below_mid = value < mid_value
        local bar = ''
    
        if is_below_mid then
            segment_count_low = bar_length - math.floor((mid_value - value) / one_segment)
        
            -- building the separate bar segments when value is lower than mid_value
            local green_bar = ''
            local yellow_bar = ''
            local red_bar = ''

            if segment_count_low > low_red_zone then 
                green_bar = '<span style="color: green;">' .. string.rep('■', low_green_zone) .. '</span>'
                yellow_bar = '<span style="color: yellow">' .. string.rep('■', low_yellow_zone - low_green_zone) .. '</span>'
                red_bar = '<span style="color: Red;">' .. string.rep('■', low_red_zone - low_yellow_zone) .. '</span>'
            elseif segment_count_low > low_green_zone then 
                green_bar = '<span style="color: #CCFDA9 ;">' .. string.rep('■', low_green_zone - segment_count) .. '</span>' ..
                            '<span style="color: Green;">' .. string.rep('■', segment_count) .. '</span>'
                yellow_bar = '<span style="color: #F4FDA9 ;">' .. string.rep('■', low_yellow_zone - low_green_zone) .. '</span>'
                red_bar = '<span style="color: #FDD2A9;">' .. string.rep('■', low_red_zone - low_yellow_zone) .. '</span>' 
            elseif segment_count_low <= low_green_zone and segment_count_low > low_yellow_zone - low_green_zone then 
                green_bar = '<span style="color: Green;">' .. string.rep('■', low_green_zone) .. '</span>'
                yellow_bar = '<span style="color: #F4FDA9 ;">' .. string.rep('■', low_yellow_zone - segment_count) .. '</span>' ..
                             '<span style="color: Yellow;">' .. string.rep('■', segment_count - low_green_zone) .. '</span>'
                red_bar = '<span style="color: #FDD2A9;">' .. string.rep('■', low_red_zone - low_yellow_zone) .. '</span>'
            elseif segment_count_low <= low_red_zone - low_yellow_zone then
                green_bar = '<span style="color: Green;">' .. string.rep('■', low_green_zone) .. '</span>'
                yellow_bar = '<span style="color: Yellow;">' .. string.rep('■', low_yellow_zone - low_green_zone) .. '</span>'
                red_bar = '<span style="color: #FDD2A9;">' .. string.rep('■', low_red_zone - segment_count) .. '</span>' ..
                          '<span style="color: Red;">' .. string.rep('■', segment_count - low_yellow_zone) .. '</span>'
            end
            -- assemble bar        
            bar = '◄' .. red_bar .. yellow_bar .. green_bar .. '►' 

        else
            -- Building the separate bar segments when value is higher than mid_value
            local green_bar = ''
            local yellow_bar = ''
            local red_bar = ''
        
            if segment_count <= high_green_zone then
                green_bar = '<span style="color: Green;">' .. string.rep('■', segment_count) .. '</span>' ..
                            '<span style="color: #CCFDA9 ;">' .. string.rep('■', high_green_zone - segment_count) .. '</span>'
                yellow_bar = '<span style="color: #F4FDA9 ;">' .. string.rep('■', high_yellow_zone - high_green_zone) .. '</span>'
                red_bar = '<span style="color: #FDD2A9 ;">' .. string.rep('■', high_red_zone - high_yellow_zone) .. '</span>'
            elseif segment_count > high_green_zone and segment_count <= high_yellow_zone then -- 13>7= ok 13<=11=not
                green_bar = '<span style="color: Green;">' .. string.rep('■', high_green_zone) .. '</span>'
                yellow_bar = '<span style="color: Yellow;">' .. string.rep('■', segment_count - high_green_zone) .. '</span>' ..
                             '<span style="color: #F4FDA9 ;">' .. string.rep('■', high_yellow_zone - segment_count) .. '</span>'
                red_bar = '<span style="color: #FDD2A9 ;">' .. string.rep('■', high_red_zone - high_yellow_zone) .. '</span>'
            elseif segment_count > high_yellow_zone then --13>11=ok
                green_bar = '<span style="color: Green;">' .. string.rep('■', high_green_zone) .. '</span>'
                yellow_bar = '<span style="color: Yellow;">' .. string.rep('■', high_yellow_zone - high_green_zone) .. '</span>'
                red_bar = '<span style="color: Red;">' .. string.rep('■', segment_count - high_yellow_zone) .. '</span>' ..
                          '<span style="color: #FDD2A9 ;">' .. string.rep('■', high_red_zone - segment_count) .. '</span>'
            elseif segment_count > high_red_zone then
                green_bar = '<span style="color: Green;">' .. string.rep('■', high_green_zone) .. '</span>'
                yellow_bar = '<span style="color: Yellow;">' .. string.rep('■', high_yellow_zone - high_green_zone) .. '</span>'
                red_bar = '<span style="color: Red;">' .. string.rep('■', segment_count - high_yellow_zone) .. '</span>'
            end
            -- assemble bar
            bar = '◄' .. green_bar .. yellow_bar .. red_bar .. '►'
        end
        return bar
    end 
    
-- the next 2 functions create the texts around the bargraph(s).
-- when the bar_length is altered or if there is a mismach between the bargraph and the numbers..
-- this can be adjusted by changing the number after the word 'segment_count'..  a lower value wil adjust to the left 
-- the adjustment has to be done 4 times per function
-- an easy way to test is by giving a fixed known value to voltageL1 like 200; 229.9; 230,1; 260
-- something like: local voltageL1 = 200 --domoticz.utils.round(domoticz.devices(28).voltage,1) in the beginning of the script
-- after all done remove the '200 --' but that should be obvious...
-- resetting the bargraph can be done by removing bargraph volt.lua file from the domoticz\scripts\dzVents\data directory (in my docker enviroment..)
-- this can be helpfull to do after the adjustments as discribed above or if there was a power outage.. 
-- or just wait till the day is over, than all values are reset to the initial ones

        -- function to get the bottom bar to be displayed at the correct place 
        local function create_bottom_bar(value_posd, blocks)
        bottom_bar_arrow  = ''
        bottom_bar_value  = ''
        blockpad = string.rep(' ', blocks)
        value = value_posd       
        -- stop value to get lower or higher than outer values of bargraph        
        if value_posd < mid_value and value_posd < low_value then
            value_posd = low_value end
        if value_posd > mid_value and value_posd > high_value then
            value_posd = high_value end   
        local one_segment = (high_value - low_value) / (2 * bar_length)
        local segment_count = math.abs(math.floor((value_posd - low_value) / one_segment))
        if value_posd < mid_value then        
            --  when value_posd is lower than mid_value   
            if (segment_count - 2) < (bar_length / 2) then 
                -- value on right side of arrow like ▲ 218.4V
                -- bar_length is 17 = segment_count + 1; + 1
                -- bar_length is 15 = segment_count + 1; + 1
                -- bar_length is 13 = segment_count + 1; + 1
                bottom_bar_arrow = string.rep(' ', segment_count + 1) .. '▲'
				bottom_bar_value = string.rep(' ', segment_count + 1) .. '⁞ <span style="background-color: Aquamarine;">' .. domoticz.utils.round(value ,1) .. 'V</span>' 
            else
                 -- value on left side of arrow like 228.7V ▲ 
                 -- bar_length is 17 = segment_count + 1; - 6
                 -- bar_length is 15 = segment_count + 1; - 6
                 -- bar_length is 13 = segment_count + 1; - 6
                 bottom_bar_arrow = string.rep(' ', segment_count + 1).. '▲'
				 bottom_bar_value = string.rep(' ', segment_count - 6) .. '<span style="background-color: Aquamarine;">' .. domoticz.utils.round(value,1) .. 'V</span> ⁞'
            end
        else 
            --  when value_posd is higher than mid_value
            if bar_length >= segment_count - 6   then
                -- value on right side of arrow like ▲ 231.3V
                -- bar_length is 17 = segment_count + 5; + 5
                -- bar_length is 15 = segment_count + 4; + 4
                -- bar_length is 13 = segment_count + 4; + 4
                bottom_bar_arrow = string.rep(' ', segment_count + 5) .. '▲'
				bottom_bar_value = string.rep(' ', segment_count + 5) .. '⁞ <span style="background-color: Aquamarine;">' ..  domoticz.utils.round(value,1) .. 'V</span>' 
            else
                -- value on left side of arrow like 248.2V ▲
                -- bar_length is 17 = segment_count + 4; - 3
                -- bar_length is 15 = segment_count + 4; - 3
                -- bar_length is 13 = segment_count + 4; - 3
                bottom_bar_arrow = string.rep(' ', segment_count + 4) .. '▲'
				bottom_bar_value = string.rep(' ', segment_count - 3) .. '<span style="background-color: Aquamarine;">' .. domoticz.utils.round(value,1) .. 'V</span> ⁞'
            end
        end
        return blockpad .. bottom_bar_arrow .. '\n' .. blockpad .. bottom_bar_value
        end


    
         -- function to get the historic values to be displayed at the correct place swapping the place of the arrow and value if necessary         
        local function create_top_bar(value_posu)
            
            -- stop value to get lower or higher than outer values of bargraph 
           value = value_posu     
            if value_posu < mid_value and value_posu < low_value then
                value_posu = low_value end
            if value_posu > mid_value and value_posu > high_value then
                value_posu = high_value end             
            local one_segment = (high_value - low_value) / (2 * bar_length)
            local segment_count = math.abs(math.floor((value_posu - low_value) / one_segment))
            if value_posu < mid_value then 
                --  when value_posu is lower than mid_value
                if segment_count < bar_length / 2 then 
                    -- value on right side of arrow = ▼ 208.8
                    -- bar_length is 17 = segment_count + 1 and segment_count - 4 
                    -- bar_length is 15 = segment_count + 1 and segment_count - 4
                    -- bar_length is 13 = segment_count + 1 and segment_count - 4
                    top_bar = string.rep(' ', segment_count + 1) .. '▼ ' ..  domoticz.utils.round(value,1) .. 'V' .. string.rep(' ', (bar_length - segment_count) -4)
                else  
                    -- value on left side of arrow = 226.1V ▼
                    -- bar_length is 17 = segment_count - 6 and segment_count +3
                    -- bar_length is 15 = segment_count - 6 and segment_count +3
                    -- bar_length is 13 = segment_count - 6 and segment_count +3
                    top_bar = string.rep(' ', segment_count - 6) ..  domoticz.utils.round(value,1) .. 'V ▼' .. string.rep(' ', (bar_length - segment_count) +3)
                end
            else 
                -- when value_posu is higher than mid_value
                if bar_length >= segment_count - 8  then
                    -- value on right side of arrow = ▼ 241.8V
                    -- bar_length is 17 = segment_count - 18
                    -- bar_length is 15 = segment_count - 16
                    -- bar_length is 13 = segment_count - 14
                    top_bar = string.rep(' ', segment_count - 18) .. '▼ ' ..  domoticz.utils.round(value,1) .. 'V'
                else
                    -- value on left side of arrow = 250.5V ▼
                    -- bar_length is 17 = segment_count - 25
                    -- bar_length is 15 = segment_count - 23
                    -- bar_length is 13 = segment_count - 21
                    top_bar = string.rep(' ', segment_count - 25) ..  domoticz.utils.round(value,1) .. 'V ▼'
                end 
            end
            return top_bar        
        end  
        -- create invisible blocks to allign the 2nd and 3rd bargraph in the textdevice 
        padd = string.rep(' ', padding)
    
-->-->-->- building bargraph
        -- when using 1 phase comment the 2nd and 3rd "local bargraph" line out! (set -- in front of local..)
        -- when using 2 phase comment the 3rd "local bargraph line" out!
        local bargraph = create_top_bar(voltageL1_min) .. create_top_bar(voltageL1_max) .. '\n' .. createBargraph(voltageL1_min) .. 'L1' .. createBargraph(voltageL1_max) .. '\n' .. create_bottom_bar(voltageL1, 0) .. '\n' 
        local bargraph = bargraph ..'――――――――――――――――――――――――――――――――――――――――\n' .. padd .. create_top_bar(voltageL2_min) .. create_top_bar(voltageL2_max) .. '\n' .. padd  .. createBargraph(voltageL2_min) .. 'L2' .. createBargraph(voltageL2_max) .. '\n' ..  create_bottom_bar(voltageL2, padding) .. '\n'
        local bargraph = bargraph .. padd .. '――――――――――――――――――――――――――――――――――――――――\n'.. padd .. create_top_bar(voltageL3_min) .. create_top_bar(voltageL3_max) .. '\n' .. padd  .. createBargraph(voltageL3_min) .. 'L3' .. createBargraph(voltageL3_max) .. '\n'.. create_bottom_bar(voltageL3, padding)

        -- giving the bargraph the correct font and settings
        local final_bar = '<span style="font-family: Consolas; font-weight: bold; font-size: 13px;">' .. bargraph .. '</span>' 

        -- update text_device
        text_device.updateText(final_bar)
    
-->-->-->- Clear the log of the textdevice, put in the right idx of the textdevice!!  
        domoticz.openURL('http://127.17.0.2:80/json.htm?type=command&param=clearlightlog&idx=337')  
    end

}
Basically the trigger is done by the P1 device and once triggered the values of the voltage devices are read and sent to a text-device, the log of the text-device is cleared at the end of the script.

You have to create a text-device set all the correct IDX numbers! (6 times total..)

On top of a bar are the lowest and highest voltages shown, these numbers are reset at midnight.
The value under the bar is the current voltage for the phase..

The script can also be altered to show 1 or 2 phase voltage supply's and set to work with 120 volt as stated in the comments in the script.(this i did not test this too thoroughly tho..)

I use a chrome browser to display my Domoticz and at my end this works great, on mobile devices the view is distorted a bit, also there could be problems if the font Consolas is not present on your setup, for this reason i did not post this earlier..
See also viewtopic.php?t=42390 and viewtopic.php?t=42360 for similar projects..
SolarEdge ModbusTCP - 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