Central Heating Control with StellaZ

For heating/cooling related questions in Domoticz

Moderator: leecollings

pdjm43
Posts: 30
Joined: Friday 12 December 2014 11:31
Target OS: Raspberry Pi / ODroid
Domoticz version:
Location: UK
Contact:

Central Heating Control with StellaZ

Post by pdjm43 »

Hi, I decided to automate my home heating with a Raspberry Pi. I have got 6 StellaZ radiator valves and an Aeon Labs Z-Wave USB stick v2.
So far in two days I have got the valves running but manually setting the setpoint for day/night. I have wired an 8 relay low switching opto board to the GPIO. I have a 1wire DS18B20 connected.
I will have a DS18B20 in each room for heat control and PIR devices hardwired into GPIO which will act as room occupied sensors for setpoints.
I found a script yesterday for heating control but now I can't find the post again, it looks at multiple zones with temp sensors and setpoints for each then switching a boiler output with hysteresis. This is running and I managed to write a script to invert the output for my GPIO relay, which I know could easily live in the heating script.

Code: Select all

-- boiler on/off GPIO device script
commandArray = {}
if devicechanged['Boiler']=='On' then
	commandArray['BoilerGPIO']='Off'
end
if devicechanged['Boiler']=='Off' then      
        commandArray['BoilerGPIO']='On'
end
return commandArray
I am an automation engineer, but I work with industrial logic controllers. I know exactly what controls I want but I don't have a clue about scripting syntax. Very steep learning curve for my old head.

My problem is that blockly won't do the math for temperature setpoints.

I am posting this script because I can't find the owner now (sorry). I know that this script could set the valve setpoints as it loops, when it sees that heat is needed in that zone it can set a rad valve to 25 and when heat is not needed it can set a rad valve to 15. This will work well because the StellaZ are stuck in comfort mode and a big change should create open and close events rather than feathering the rad valve.

Please help me with this, I really can't get my head around strings to numbers.

Code: Select all

--[[

Heating Coordinator Script
==========================

This script is used to manage the firing of a boiler to support multiple zone requirements using temperature sensor readings and thermostat setpoints in each.

User variable requirements
--------------------------

Name: "HeatingCoordinator_ThermostatNamePrefix"
Type: "String"
Description: Defines the thermostat device name prefix, a device with a name made from this prefix followed by the zone should be present
Example: "Therm_"

Name: "HeatingCoordinator_TempSensorNamePrefix"
Type: "String"
Description: Defines the temp sensor device name prefix, a device with a name made from this prefix followed by the zone should be present
Example: "Temp_"

Name: "HeatingCoordinator_BoilerReceiverSwitchName"
Type: "String"
Description: Defines the boiler receiver device naming
Example: "Boiler"

Name: "HeatingCoordinator_Zones"
Type: "String"
Description: Defines the zone names in a comma delimited list with no spaces
Example: "LivingRoom,Kitchen,BedroomFrontLeft,BedroomFrontRight,BedroomBackLeft"

Name: "HeatingCoordinator_BoilerStateChangeWaitTime"
Type: "Integer"
Description: Defines the length of time in seconds before a boiler state change is allowed after a previous update
Example: 300

Name: "HeatingCoordinator_BoilerAliveTime"
Type: "Integer"
Description: Defines the length of time in seconds since a previous boiler state change before a keep alive update is required. Some receivers will switch off automatically after a given time if not told to stay on, this is normally done by a room thermostat but without one this is required.
Example: 1800

Name: "HeatingCoordinator_TempHysterisis"
Type: "Float"
Description: Defines the amount of temperature allowance either side of the on / off heating states to stop constant switching
Example: 0.5

Name: "HeatingCoordinator_LogActivity"
Type: "Integer"
Description: Defines if all activity is logged ot nor, 1 = yes and 0 = No. Activity relating to state changes is always logged however.
Example: 1

]]


commandArray = {}

function splitString(str, delim, maxNb)
    -- Eliminate bad cases...
    if string.find(str, delim) == nil then
        return { str }
    end
    if maxNb == nil or maxNb < 1 then
        maxNb = 0    -- No limit
    end
    local result = {}
    local pat = "(.-)" .. delim .. "()"
    local nb = 0
    local lastPos
    for part, pos in string.gmatch(str, pat) do
        nb = nb + 1
        result[nb] = part
        lastPos = pos
        if nb == maxNb then break end
    end
    -- Handle the last field
    if nb ~= maxNb then
        result[nb + 1] = string.sub(str, lastPos)
    end
    return result
end

function getLastUpdatedTime(deviceName)

   lastUpdatedString = otherdevices_lastupdate[deviceName]

   year = string.sub(lastUpdatedString, 1, 4)
   month = string.sub(lastUpdatedString, 6, 7)
   day = string.sub(lastUpdatedString, 9, 10)
   hour = string.sub(lastUpdatedString, 12, 13)
   minutes = string.sub(lastUpdatedString, 15, 16)
   seconds = string.sub(lastUpdatedString, 18, 19)

   return os.time{year=year, month=month, day=day, hour=hour, min=minutes, sec=seconds}

end

function logtext(text, override)
   if logActivity == 1 then
      print("(HeatingCoordinator) "..text)
   end
end

function getZoneHeatNeeded(zoneName)

   local zoneHeatNeeded = "NotApplicable"
   local zoneSetPointMax = -999
   local zoneTempMin = 999

   for deviceName, deviceValue in pairs(otherdevices) do

      if string.find(deviceName, thermostatNamePrefix..zoneName) ~= nil then

         -- device value needs refetching via svalues
         deviceValue = otherdevices_svalues[deviceName]

         -- if zone thermostat has more than just one value get the first one
         deviceValueDelimited = string.find(deviceValue, ";")

         if deviceValueDelimited == nil then
            deviceValue = tonumber(deviceValue)
         else
            deviceValue = tonumber(string.sub(deviceValue, 0, deviceValueDelimited - 1))
         end

         -- determine the max setpoint for the zone
         if zoneSetPointMax < deviceValue then
            zoneSetPointMax = deviceValue 
         end

      end

      if string.find(deviceName, tempSensorNamePrefix..zoneName) ~= nil then

         -- device value needs refetching via svalues
         deviceValue = otherdevices_svalues[deviceName]

         -- if zone temp has more than just one value get the first one
         deviceValueDelimited = string.find(deviceValue, ";")
         if deviceValueDelimited == nil then
            deviceValue = tonumber(deviceValue)
         else
            deviceValue = tonumber(string.sub(deviceValue, 0, deviceValueDelimited - 1))
         end

         -- determine the min temp for the zone
         if zoneTempMin > deviceValue then
            zoneTempMin = deviceValue 
         end

      end
   end

   -- if nothing valid for zone readings assume not applicable
   if (zoneSetPointMax == -999 or zoneTempMin == 999) then
      zoneHeatNeeded = "NotApplicable"
      logtext(string.format(" Zone: Name = '%s', Temp = '%s', SetPoint = '%s', Heat Needed = '%s' (Device Reading Error)", zoneName, zoneTempMin, zoneSetPointMax, zoneHeatNeeded), true)
   end

   -- determine heating requirements including hysterisis
   if (zoneTempMin < (zoneSetPointMax - tempHysteresis)) then
      zoneHeatNeeded = "Yes"
   elseif (zoneTempMin > (zoneSetPointMax + tempHysteresis)) then      
      zoneHeatNeeded = "No"
   else
      zoneHeatNeeded = "NotApplicable"
   end

   logtext(string.format(" Zone: Name = '%s', Temp = '%s', SetPoint = '%s', Heat Needed = '%s'", zoneName, zoneTempMin, zoneSetPointMax, zoneHeatNeeded))

   return zoneHeatNeeded

end

function processZones()

   -- loop our zones and see if any needs heat
   boilerState = "NotSet"
   
   for heatingZonesIndex = 1, #heatingZones do

      -- heat needed to turn on boiler
      zoneHeatNeeded = getZoneHeatNeeded(heatingZones[heatingZonesIndex])   

      if boilerState ~= "On" then
         if (zoneHeatNeeded == "Yes") then
            boilerState = "On"
         elseif (zoneHeatNeeded == "NotApplicable") then
            boilerState = "NotApplicable"
         elseif (zoneHeatNeeded == "No" and boilerState ~= "NotApplicable") then
            boilerState = "Off"
         end
      end
   end

   boilerWaitTime = os.difftime(os.time(),getLastUpdatedTime(boilerReceiverSwitchName))

   if boilerState == "On" then
      if boilerState ~= otherdevices[boilerReceiverSwitchName] then
         if boilerWaitTime >= boilerStateChangeWaitTime then
            commandArray[boilerReceiverSwitchName] = boilerState
            logtext("Boiler State Change: Off -> HeatOn", true)
         else
            logtext(string.format("Boiler State Change: Off -> HeatOn (in %s secs)",boilerStateChangeWaitTime - boilerWaitTime), true)
         end
      else
         if boilerWaitTime >= boilerAliveTime then
            commandArray[boilerReceiverSwitchName] = boilerState
            logtext("Boiler State Remains: HeatOn (Keep Alive Update)", true)
         else
            logtext("Boiler State Remains: HeatOn")
         end
      end
   elseif boilerState == "Off" then
      if boilerState ~= otherdevices[boilerReceiverSwitchName] then
         if boilerWaitTime >= boilerStateChangeWaitTime then
            commandArray[boilerReceiverSwitchName] = boilerState
            logtext("Boiler State Change: HeatOn -> Off", true)
         else
            logtext(string.format("Boiler State Change: HeatOn -> Off (in %s secs)",boilerStateChangeWaitTime - boilerWaitTime), true)
         end
      else
         logtext("Boiler State Remains: Off")
      end

   elseif boilerState == "NotApplicable" then

      if boilerWaitTime >= boilerAliveTime and otherdevices[boilerReceiverSwitchName] == "On" then
         commandArray[boilerReceiverSwitchName] = "On"
         logtext("Boiler State Remains: HeatOn (Hysterisis / Keep Alive Update)", true)
      else
         if otherdevices[boilerReceiverSwitchName] == "On" then
            logtext("Boiler State Remains: HeatOn (Hysterisis)") 
      else
            logtext("Boiler State Remains: Off (Hysterisis)")
         end
      end

   end

   return
end

-- User variable population
thermostatNamePrefix = uservariables["HeatingCoordinator_ThermostatNamePrefix"]
tempSensorNamePrefix = uservariables["HeatingCoordinator_TempSensorNamePrefix"]
boilerReceiverSwitchName = uservariables["HeatingCoordinator_BoilerReceiverSwitchName"]
heatingZones = splitString(uservariables["HeatingCoordinator_Zones"],",")
boilerStateChangeWaitTime = uservariables["HeatingCoordinator_BoilerStateChangeWaitTime"]
boilerAliveTime = uservariables["HeatingCoordinator_BoilerAliveTime"]
tempHysteresis = uservariables["HeatingCoordinator_TempHysterisis"]
logActivity = uservariables["HeatingCoordinator_LogActivity"]
-- Process heating requirements
processZones()

return commandArray
Sorry for the massive first post :D
Last edited by pdjm43 on Sunday 18 January 2015 9:11, edited 1 time in total.
pdjm43
Posts: 30
Joined: Friday 12 December 2014 11:31
Target OS: Raspberry Pi / ODroid
Domoticz version:
Location: UK
Contact:

Re: Central Heating Control, Help Needed

Post by pdjm43 »

Ok, all my 1wire probes are installed and working.
The difference with the StellaZ temp reading from closed to open valve is massive, so I can't really control the system with real room required temperatures because the rad might shoot to 24 when the room is 19.5.
I need a script that sets the overinflated setpoint with persistence until the StellaZ reports back the setpoint.

The logic I want is does the room need heat? setpoint=30. Is the room at temperature? setpoint=15.
setpoints from variables so that I can write them at times of the day and with future added proximity detection.
Added into the large heating script above would make an excellent tool.

Could anyone write that for me please?
pdjm43
Posts: 30
Joined: Friday 12 December 2014 11:31
Target OS: Raspberry Pi / ODroid
Domoticz version:
Location: UK
Contact:

Re: Central Heating Control, Help Needed

Post by pdjm43 »

Ok, Iv'e made a script pulling two string to integers, but one is x.xx and the other is x.x, trying to shave the extra decimal place off now.
pdjm43
Posts: 30
Joined: Friday 12 December 2014 11:31
Target OS: Raspberry Pi / ODroid
Domoticz version:
Location: UK
Contact:

Re: Central Heating Control, Help Needed

Post by pdjm43 »

Ok, I have integrated the GPIO output invert into the main script and added a variable for the StellaZ setpoint.

But, I can't find the right syntax/value to write to the valve setpoint, any help? Is there a way to see the value sent from Domoticz when the setpoint is changed manually on the web page?

Thanks.
kaivalagi
Posts: 21
Joined: Tuesday 25 November 2014 11:31
Target OS: Raspberry Pi / ODroid
Domoticz version:
Location: Norwich, United Kingdom
Contact:

Re: Central Heating Control, Help Needed

Post by kaivalagi »

Hi, I wrote the script you've tried to use so hopefully can help.

I assume you created a function to do the switch value inverting and then use that rather than setting the boiler state directly where it is done right now?

Not sure on setting a setpoint through a script, never had to do it, but I am assuming you either need to do this:

commandArray["ThermostateDevice"]=20.5

Or this:

commandArray["ThermostatDevice_SetPoint"]=20.5

I know when retrieving the value I had to use just the device name but you need to use the SetPoint suffix here?

Why don't you post your modified script here so I can better understand what you have done and are trying to do?

btw, the original thread I posted the script on is here: http://www.domoticz.com/forum/viewtopic.php?f=24&t=4190
pdjm43
Posts: 30
Joined: Friday 12 December 2014 11:31
Target OS: Raspberry Pi / ODroid
Domoticz version:
Location: UK
Contact:

Re: Central Heating Control, Help Needed

Post by pdjm43 »

Hi kaivalagi,

I am struggling now.
I have taken off the StellaZs and thought I could use your code for boiler control until I found what you just posted.
The state I'm in now is the code is limited to 1 zone entry in the variables. With adding lots of prints in the code I can see the setpoint works fine but the temperature is reading twice once the correct value and the other a lower wrong value, these are mostly in order of good value then bad which then calls for heat.

I will tidy it up and post now. Been at it since 4am.
pdjm43
Posts: 30
Joined: Friday 12 December 2014 11:31
Target OS: Raspberry Pi / ODroid
Domoticz version:
Location: UK
Contact:

Re: Central Heating Control, Help Needed

Post by pdjm43 »

value written from the same print command

2014-12-13 12:21:24 LUA: tmp1 : 18.5
2014-12-13 12:21:24 LUA: tmp1 : 21.4
2014-12-13 12:21:24 (Probe) Temp (Temp_FrontBed)
2014-12-13 12:21:24 LUA: tmp1 : 18.5
2014-12-13 12:21:24 LUA: tmp1 : 21.4
2014-12-13 12:21:24 (Probe) Temp (Temp_SideBed)
2014-12-13 12:21:24 LUA: tmp1 : 18.5
2014-12-13 12:21:24 LUA: tmp1 : 21.4
2014-12-13 12:21:24 (Probe) Temp (Temp_Kitchen)
2014-12-13 12:21:24 LUA: tmp1 : 18.5
2014-12-13 12:21:24 LUA: tmp1 : 21.3
2014-12-13 12:21:24 (Probe) Temp (Temp_Lounge)
2014-12-13 12:21:54 LUA: tmp1 : 21.3
2014-12-13 12:21:54 LUA: tmp1 : 18.5
2014-12-13 12:21:54 (Probe) Temp (Temp_FrontBed

output with logging on and print removed

2014-12-13 12:24:55 LUA: (HeatingCoordinator) Zone: Name = 'Lounge', Temp = '18.5', SetPoint = '19', Heat Needed = 'Yes'
2014-12-13 12:24:55 LUA: (HeatingCoordinator) Boiler State Remains: HeatOn
2014-12-13 12:24:55 (Probe) Temp (Temp_FrontBed)
2014-12-13 12:24:55 LUA: (HeatingCoordinator) Zone: Name = 'Lounge', Temp = '18.5', SetPoint = '19', Heat Needed = 'Yes'
2014-12-13 12:24:55 LUA: (HeatingCoordinator) Boiler State Remains: HeatOn
2014-12-13 12:24:55 (Probe) Temp (Temp_SideBed)
2014-12-13 12:24:55 LUA: (HeatingCoordinator) Zone: Name = 'Lounge', Temp = '18.5', SetPoint = '19', Heat Needed = 'Yes'
pdjm43
Posts: 30
Joined: Friday 12 December 2014 11:31
Target OS: Raspberry Pi / ODroid
Domoticz version:
Location: UK
Contact:

Re: Central Heating Control, Help Needed

Post by pdjm43 »

This is the modified script working fine to invert my gpio relay, not tested the setpoint output with a valve yet.
Just deployed your original code with a print command after getting the temp and the result is the same as mine.
Ta.

Code: Select all

--[[

Heating Coordinator Script - written by kaivalagi
==========================

]]

commandArray = {}
function splitString(str, delim, maxNb)
    -- Eliminate bad cases...
    if string.find(str, delim) == nil then
        return { str }
    end
    if maxNb == nil or maxNb < 1 then
        maxNb = 0    -- No limit
    end
    local result = {}
    local pat = "(.-)" .. delim .. "()"
    local nb = 0
    local lastPos
    for part, pos in string.gmatch(str, pat) do
        nb = nb + 1
        result[nb] = part
        lastPos = pos
        if nb == maxNb then break end
    end
    -- Handle the last field
    if nb ~= maxNb then
        result[nb + 1] = string.sub(str, lastPos)
    end
    return result
end

function getLastUpdatedTime(deviceName)

   lastUpdatedString = otherdevices_lastupdate[deviceName]

   year = string.sub(lastUpdatedString, 1, 4)
   month = string.sub(lastUpdatedString, 6, 7)
   day = string.sub(lastUpdatedString, 9, 10)
   hour = string.sub(lastUpdatedString, 12, 13)
   minutes = string.sub(lastUpdatedString, 15, 16)
   seconds = string.sub(lastUpdatedString, 18, 19)

   return os.time{year=year, month=month, day=day, hour=hour, min=minutes, sec=seconds}

end

function logtext(text)
   if logActivity == 1 then
      print("(HeatingCoordinator) "..text)
   end
end

function getZoneHeatNeeded(zoneName)

   local zoneHeatNeeded = "NotApplicable"
   local zoneSetPointMax = -999
   local zoneTempMin = 999

   for deviceName, deviceValue in pairs(otherdevices) do

      if string.find(deviceName, thermostatNamePrefix..zoneName) ~= nil then

         -- device value needs refetching via svalues
         deviceValue = otherdevices_svalues[deviceName]

         -- if zone thermostat has more than just one value get the first one
         deviceValueDelimited = string.find(deviceValue, ";")

         if deviceValueDelimited == nil then
            deviceValue = tonumber(deviceValue)
         else
            deviceValue = tonumber(string.sub(deviceValue, 0, deviceValueDelimited - 1))
         end

         -- determine the max setpoint for the zone
         if zoneSetPointMax < deviceValue then
            zoneSetPointMax = deviceValue 
         end

      end
------------------------------------------------------------------------------------

      if string.find(deviceName, tempSensorNamePrefix..zoneName) ~= nil then

         -- device value needs refetching via svalues
         deviceValue = otherdevices_svalues[deviceName]
--print('tmp1 : ' .. tostring(deviceValue))
         -- if zone temp has more than just one value get the first one
         deviceValueDelimited = string.find(deviceValue, ";")
         if deviceValueDelimited == nil then
            deviceValue = tonumber(deviceValue)
         else
            deviceValue = tonumber(string.sub(deviceValue, 0, deviceValueDelimited - 1))
         end

         -- determine the min temp for the zone
         if zoneTempMin > deviceValue then
            zoneTempMin = deviceValue 
         end

      end
   end

   -- if nothing valid for zone readings assume not applicable
   if (zoneSetPointMax == -999 or zoneTempMin == 999) then
      zoneHeatNeeded = "NotApplicable"
      logtext(string.format(" Zone: Name = '%s', Temp = '%s', SetPoint = '%s', Heat Needed = '%s' (Device Reading Error)", zoneName, zoneTempMin, zoneSetPointMax, zoneHeatNeeded), true)
   end

   -- determine heating requirements including hysterisis
   if (zoneTempMin < (zoneSetPointMax - tempHysteresis)) then
      zoneHeatNeeded = "Yes"
   elseif (zoneTempMin > (zoneSetPointMax + tempHysteresis)) then      
      zoneHeatNeeded = "No"
   else
      zoneHeatNeeded = "NotApplicable"
   end

   logtext(string.format(" Zone: Name = '%s', Temp = '%s', SetPoint = '%s', Heat Needed = '%s'", zoneName, zoneTempMin, zoneSetPointMax, zoneHeatNeeded))

   return zoneHeatNeeded
-----------------------------------------------------------------------------------------------
end

function processZones()

   -- loop our zones and see if any needs heat
   boilerState = "NotSet"
   
  for heatingZonesIndex = 1, #heatingZones do

      -- heat needed to turn on boiler
      zoneHeatNeeded = getZoneHeatNeeded(heatingZones[heatingZonesIndex])   

      if boilerState ~= "On" then
         if (zoneHeatNeeded == "Yes") then
            boilerState = "On"
         elseif (zoneHeatNeeded == "NotApplicable") then
            boilerState = "NotApplicable"
         elseif (zoneHeatNeeded == "No" and boilerState ~= "NotApplicable") then
            boilerState = "Off"
         end
      end
   end

   boilerWaitTime = os.difftime(os.time(),getLastUpdatedTime(boilerReceiverSwitchName))

   if boilerState == "On" then
      if boilerState ~= otherdevices[boilerReceiverSwitchName] then
         if boilerWaitTime >= boilerStateChangeWaitTime then
            commandArray[boilerReceiverSwitchName] = boilerState
            commandArray['BoilerGpio'] = 'Off'
            commandArray[tempSetpointNamePrefix]=30
            logtext("Boiler State Change: Off -> HeatOn", true)
         else
            logtext(string.format("Boiler State Change: Off -> HeatOn (in %s secs)",boilerStateChangeWaitTime - boilerWaitTime), true)
         end
      else
         if boilerWaitTime >= boilerAliveTime then
            commandArray[boilerReceiverSwitchName] = boilerState
            commandArray['BoilerGpio'] = 'Off'
            commandArray[tempSetpointNamePrefix]=30
            logtext("Boiler State Remains: HeatOn (Keep Alive Update)", true)
         else
            logtext("Boiler State Remains: HeatOn")
         end
      end
   elseif boilerState == "Off" then
      if boilerState ~= otherdevices[boilerReceiverSwitchName] then
         if boilerWaitTime >= boilerStateChangeWaitTime then
            commandArray[boilerReceiverSwitchName] = boilerState
            commandArray['BoilerGpio'] = 'On'
            commandArray[tempSetpointNamePrefix]=15
            logtext("Boiler State Change: HeatOn -> Off", true)
            print('current : ' .. tostring(otherdevices[tempSetpointNamePrefix]))
         else
            logtext(string.format("Boiler State Change: HeatOn -> Off (in %s secs)",boilerStateChangeWaitTime - boilerWaitTime), true)
         end
      else
         logtext("Boiler State Remains: Off")
      end

   elseif boilerState == "NotApplicable" then

      if boilerWaitTime >= boilerAliveTime and otherdevices[boilerReceiverSwitchName] == "On" then
         commandArray[boilerReceiverSwitchName] = "On"
         commandArray['BoilerGpio'] = 'Off'
         commandArray[tempSetpointNamePrefix]=30
         logtext("Boiler State Remains: HeatOn (Hysterisis / Keep Alive Update)", true)
      else
         if otherdevices[boilerReceiverSwitchName] == "On" then
            logtext("Boiler State Remains: HeatOn (Hysterisis)") 
      else
            logtext("Boiler State Remains: Off (Hysterisis)")
         end
      end

   end

   return
end

-- User variable population
thermostatNamePrefix = uservariables["HeatingCoordinator_ThermostatNamePrefix"]
tempSensorNamePrefix = uservariables["HeatingCoordinator_TempSensorNamePrefix"]
tempSetpointNamePrefix = uservariables["HeatingCoordinator_SetpointNamePrefix"]
boilerReceiverSwitchName = uservariables["HeatingCoordinator_BoilerReceiverSwitchName"]
heatingZones = splitString(uservariables["HeatingCoordinator_Zones"],",")
boilerStateChangeWaitTime = uservariables["HeatingCoordinator_BoilerStateChangeWaitTime"]
boilerAliveTime = uservariables["HeatingCoordinator_BoilerAliveTime"]
tempHysteresis = uservariables["HeatingCoordinator_TempHysterisis"]
logActivity = uservariables["HeatingCoordinator_LogActivity"]
-- Process heating requirements
processZones()

return commandArray
kaivalagi
Posts: 21
Joined: Tuesday 25 November 2014 11:31
Target OS: Raspberry Pi / ODroid
Domoticz version:
Location: Norwich, United Kingdom
Contact:

Re: Central Heating Control, Help Needed

Post by kaivalagi »

So you don't need to set valve setpoints at all, I'm confused too :)

Can you add a the "deviceName" to your print statement? Looks like it might be matching 2 entries?
pdjm43
Posts: 30
Joined: Friday 12 December 2014 11:31
Target OS: Raspberry Pi / ODroid
Domoticz version:
Location: UK
Contact:

Re: Central Heating Control, Help Needed

Post by pdjm43 »

Can you add a the "deviceName" to your print statement? Looks like it might be matching 2 entries?

does this mean it would read Temp_Lounge and Temp_Lounge_Rad?? that sounds like it. Oooops the zwave is disabled and the Temp_Lounge_Rad isn;t in device list. whats the syntax for print device name, I've deployed through ftp 200 times while testing, I really don't know anything. :oops:

I want to fire the StellaZ up to 30 for heat and down to 15 for not required, the therms on the valve spike up when the rad heats and turns it off although the room is not heated to setpoint. Tested the code with a valve and the setpoint code throws an integer/string write error.
pdjm43
Posts: 30
Joined: Friday 12 December 2014 11:31
Target OS: Raspberry Pi / ODroid
Domoticz version:
Location: UK
Contact:

Re: Central Heating Control, Help Needed

Post by pdjm43 »

Ok, Iv'e used your earlier code now. Modified it with the variables in the last code and it looks at each rooms thermostat, works perfect :D just need to try and find a working command to StellaZ setpoint.

2014-12-13 20:22:43 LUA: (HeatingCoordinator) Zone: Name = 'Lounge', Temp = '23.9', SetPoint = '18', Heat Needed = 'No'
2014-12-13 20:22:43 LUA: (HeatingCoordinator) Zone: Name = 'Kitchen', Temp = '21', SetPoint = '18', Heat Needed = 'No'
2014-12-13 20:22:43 LUA: (HeatingCoordinator) Zone: Name = 'FrontBed', Temp = '20.8', SetPoint = '18', Heat Needed = 'No'
2014-12-13 20:22:43 LUA: (HeatingCoordinator) Zone: Name = 'SideBed', Temp = '20', SetPoint = '18', Heat Needed = 'No'
2014-12-13 20:22:43 LUA: (HeatingCoordinator) Zone: Name = 'MasterBed', Temp = '19.5', SetPoint = '18', Heat Needed = 'No'
2014-12-13 20:22:43 LUA: (HeatingCoordinator) Zone: Name = 'Hallway', Temp = '20.8', SetPoint = '18', Heat Needed = 'No'
2014-12-13 20:22:43 LUA: (HeatingCoordinator) Boiler State: Off - Unchanged

Code: Select all

commandArray = {}

-- Constants
boilerReceiverSwitchName = uservariables["HeatingCoordinator_BoilerReceiverSwitchName"]
heatingZones = {"Lounge","Kitchen","FrontBed","SideBed","MasterBed","Hallway"}
zoneThermostatName = uservariables["HeatingCoordinator_ThermostatNamePrefix"]
zoneTempSensorName = uservariables["HeatingCoordinator_TempSensorNamePrefix"]

boilerStateChangeWaitTime = 10 --  300 = 5 minutes
boilerAliveTime = 2700 -- 45 minutes
tempHysteresis = 0.5

function getLastUpdatedTime(deviceName)

   lastUpdatedString = otherdevices_lastupdate[deviceName]

   year = string.sub(lastUpdatedString, 1, 4)
   month = string.sub(lastUpdatedString, 6, 7)
   day = string.sub(lastUpdatedString, 9, 10)
   hour = string.sub(lastUpdatedString, 12, 13)
   minutes = string.sub(lastUpdatedString, 15, 16)
   seconds = string.sub(lastUpdatedString, 18, 19)

   return os.time{year=year, month=month, day=day, hour=hour, min=minutes, sec=seconds}

end

function printline(text)
  print("(HeatingCoordinator) "..text)
end

function getZoneHeatNeeded(zoneName)

   -- get radiator setpoint 
    zoneSetPoint = otherdevices_svalues[zoneThermostatName..zoneName]

   -- get zone temp
    zoneTemp = otherdevices_svalues[zoneTempSensorName..zoneName]

   -- if zone temp has more than just one value get the first one as this is a multisensor
--   zoneTempEnd = string.find(zoneTemp, ";")
   if zoneTempEnd ~= nil then
      zoneTemp = string.sub(zoneTemp, 0, zoneTempEnd - 1)
   end

   -- if nothing valid for zone readings assume off
   if (zoneSetPoint == nil or zoneTemp == nil) then
      return "Off"
   end

   zoneTemp = tonumber(zoneTemp)
   zoneSetPoint = tonumber(zoneSetPoint)

   -- determine heating requirements including hysterisis
   if (zoneTemp < (zoneSetPoint - tempHysteresis)) then
      zoneHeatNeeded = "Yes"
   elseif (zoneTemp > (zoneSetPoint + tempHysteresis)) then      
      zoneHeatNeeded = "No"
   else
      zoneHeatNeeded = "NotApplicable"
   end

   printline(string.format(" Zone: Name = '%s', Temp = '%s', SetPoint = '%s', Heat Needed = '%s'", zoneName, zoneTemp, zoneSetPoint, zoneHeatNeeded))

   return zoneHeatNeeded

end

function processZones()

   -- loop our zones and see if any needs heat
   boilerState = "NotSet"
   

   for heatingZonesIndex = 1, #heatingZones do

      -- heat needed to turn on boiler
      zoneHeatNeeded = getZoneHeatNeeded(heatingZones[heatingZonesIndex])   

      if boilerState ~= "On" then
         if (zoneHeatNeeded == "Yes") then
            boilerState = "On"
         elseif (zoneHeatNeeded == "NotApplicable") then
            boilerState = "NotApplicable"
         elseif (zoneHeatNeeded == "No" and boilerState ~= "NotApplicable") then
            boilerState = "Off"
         end
      end
   end

   boilerWaitTime = os.difftime(os.time(),getLastUpdatedTime(boilerReceiverSwitchName))

   if boilerState == "On" then
      if boilerState ~= otherdevices[boilerReceiverSwitchName] then
         if boilerWaitTime > boilerStateChangeWaitTime then
            commandArray[boilerReceiverSwitchName] = boilerState
            commandArray['BoilerGpio'] = 'Off'
            stateDetail = "Boiler State: Off -> HeatOn"
         else
            stateDetail = string.format("Boiler State: Off -> HeatOn (%s secs wait)",boilerStateChangeWaitTime - boilerWaitTime)
         end
      else
         if boilerWaitTime > boilerAliveTime then
            commandArray[boilerReceiverSwitchName] = boilerState
            commandArray['BoilerGpio'] = 'Off'
            stateDetail = "Boiler State: HeatOn - Keep Alive Update"
         else
            stateDetail = "Boiler State: HeatOn - Unchanged"
         end
      end
   elseif boilerState == "Off" then
      if boilerState ~= otherdevices[boilerReceiverSwitchName] then
         if boilerWaitTime > boilerStateChangeWaitTime then
            commandArray[boilerReceiverSwitchName] = boilerState
            commandArray['BoilerGpio'] = 'On'
            stateDetail = "Boiler State: HeatOn -> Off"
         else
            stateDetail = string.format("Boiler State: HeatOn -> Off (%s secs wait)",boilerStateChangeWaitTime - boilerWaitTime)
         end
      else
         stateDetail = "Boiler State: Off - Unchanged"
      end

   elseif boilerState == "NotApplicable" then

      if boilerWaitTime > boilerAliveTime and otherdevices[boilerReceiverSwitchName] == "On" then
         commandArray[boilerReceiverSwitchName] = "On"
         commandArray['BoilerGpio'] = 'Off'
         stateDetail = string.format("Boiler State: Within Hysterisis (%s) - Keep Alive Update",otherdevices[boilerReceiverSwitchName])
      else
         stateDetail = string.format("Boiler State: Within Hysterisis (%s)",otherdevices[boilerReceiverSwitchName])
      end
   end

   printline(stateDetail)

   return
end

processZones()

return commandArray
kaivalagi
Posts: 21
Joined: Tuesday 25 November 2014 11:31
Target OS: Raspberry Pi / ODroid
Domoticz version:
Location: Norwich, United Kingdom
Contact:

Re: Central Heating Control, Help Needed

Post by kaivalagi »

Change this: "--print('tmp1 : ' .. tostring(deviceValue))" to be "--print('tmp1 : ' .. deviceName..":"..tostring(deviceValue))"

Sounds like the 2 devices you have are the reason for 2 readings, can you change the name of the second "_rads" device as if it's a lower temp it will set the min temp for deciding on heating on/off...or you can exclude it through code explicitly if you know how i.e. "and deviceName ~= "Temp_Lounge_Rads" added on the end of the string.find check
pdjm43
Posts: 30
Joined: Friday 12 December 2014 11:31
Target OS: Raspberry Pi / ODroid
Domoticz version:
Location: UK
Contact:

Re: Central Heating Control, Help Needed

Post by pdjm43 »

You're spot on, even though they're not in devices Temp_Lounge_Rad etc must be in a DB.
2014-12-13 20:47:32 LUA: tmp1 : Temp_FrontBed:20.5
2014-12-13 20:47:32 LUA: tmp1 : Temp_FrontBed_Rad:18.5
Like you suggest a simple rename will fix this.

So the question is, I have the first code working the same now any way, which one is better? one is much smaller than the other.

Also do you have any thoughts on the StellaZ setpoint command?
commandArray[tempSetpointName..zoneName]=30.0
attempt to concatenate global 'zoneName' (a nil value)

It seems the zonename down there in the code doesn't exist, how do I populate it?
kaivalagi
Posts: 21
Joined: Tuesday 25 November 2014 11:31
Target OS: Raspberry Pi / ODroid
Domoticz version:
Location: Norwich, United Kingdom
Contact:

Re: Central Heating Control, Help Needed

Post by kaivalagi »

Latest is the best, as it's what I am using so if you have an issue I have a better chance of understanding why :) The latest needs no editing as it uses user variables for all the setup, obviously if you use it you'll need to create them all with the same values as you are using at the moment with your edited script

That error for assigning to the commandArray will be because "tempSetpointName" is nil i.e. non existant, shouldn't it be "thermostatNamePrefix"? i.e. the same name used in the code for getting the value through the otherdevices_svalues call
pdjm43
Posts: 30
Joined: Friday 12 December 2014 11:31
Target OS: Raspberry Pi / ODroid
Domoticz version:
Location: UK
Contact:

Re: Central Heating Control, Help Needed

Post by pdjm43 »

Ok, so I have just started putting the StellaZ valves back onto the system.
kaivalagi, I see you've written some json code to write the setpoints out to the valve. I tried your code but couldn't get the idx back, when I hardcoded the idx it worked for the one valve I tried though. But it needs to be persistent as the valve can overwrite the value when it wakes up if it doesn't take the new value.

I had a couple of bad nights with heat called to a room but the was valve closed, overheating the house.
I managed to set a variable for heat required and the zoneName in the heatingcoordinator script. I use this to check if the valve is open enough to use the heat , then control the boiler.
It's simple and I will need to create a loop with variables to make it nice. (GPIO is reversed on my relay board)

Code: Select all

raw = otherdevices_svalues['ValveFrontBed']
valve = tonumber((string.match(raw, "%d+%.?%d*"))*6.25)

if uservariables["HeatingCoordinator_Req_FrontBed"] == "On" and valve > 12.5 and otherdevices["BoilerGpio"] == "On" then

wantHeat1 = 'On'
print("valve FrontBed :" ..valve)
end

if (uservariables["HeatingCoordinator_Req_FrontBed"] == "Off" or valve < 12.5) and otherdevices["BoilerGpio"] == "Off" then

wantHeat1 = 'Off'
end
-----------------------------------------
if (wantHeat1 == "On" or wantHeat2 == "On" or wantHeat3 == "On") and otherdevices["BoilerGpio"] == "On" and otherdevices["Boiler"] == "On" then
commandArray['BoilerGpio'] = 'Off'
end

if wantHeat1 == "Off" and wantHeat2 == "Off" and wantHeat3 == "Off" and otherdevices["BoilerGpio"] == "Off" then
commandArray['BoilerGpio'] = 'On'
end

kaivalagi
Posts: 21
Joined: Tuesday 25 November 2014 11:31
Target OS: Raspberry Pi / ODroid
Domoticz version:
Location: Norwich, United Kingdom
Contact:

Re: Central Heating Control, Help Needed

Post by kaivalagi »

pdjm43 wrote:I had a couple of bad nights with heat called to a room but the was valve closed, overheating the house.
I managed to set a variable for heat required and the zoneName in the heatingcoordinator script. I use this to check if the valve is open enough to use the heat , then control the boiler.
Well done on the mods

Did the valve reset it's setpoint like you mentioned then? I assume you have a timer based setup for the setpoint changes? Could you just add more of the same setting on a regular basis in the timer maybe?
pdjm43
Posts: 30
Joined: Friday 12 December 2014 11:31
Target OS: Raspberry Pi / ODroid
Domoticz version:
Location: UK
Contact:

Re: Central Heating Control, Help Needed

Post by pdjm43 »

Well done on the mods
Did the valve reset it's setpoint like you mentioned then? I assume you have a timer based setup for the setpoint changes? Could you just add more of the same setting on a regular basis in the timer maybe?

Thanks, it's coming slowly.
Yeah, I thought to use more timers or just compare and set until they're the same.

Did you get any further with controlling the SellaZ? I'm having trouble with them stopping sending valve % state, using up the batteries quicker than an investment banker hoovers up coke and the residual heat in the valve body keeping the temp reading too high and holding off the valve.

Can we put these into direct control mode?
kaivalagi
Posts: 21
Joined: Tuesday 25 November 2014 11:31
Target OS: Raspberry Pi / ODroid
Domoticz version:
Location: Norwich, United Kingdom
Contact:

Re: Central Heating Control, Help Needed

Post by kaivalagi »

Still not got my valves yet...cash flow issue until mid feb
pdjm43
Posts: 30
Joined: Friday 12 December 2014 11:31
Target OS: Raspberry Pi / ODroid
Domoticz version:
Location: UK
Contact:

Re: Central Heating Control, Help Needed

Post by pdjm43 »

OK, this is where I am now.
I have 5 StellaZ connected and with either luck or beta updates they are all stable and have been for at least 5 days.
The heating coordinator v1 script is modified to suit me and my other script checks the StellaZ are open and able to heat the room before allowing the boiler, I'm only checking 4 rooms for now as I haven't expanded it yet.
I use the hysteresis to switch the boiler on at 0.3 but I just want to see the first rise in temperature and then switch off as the overshooot is massive otherwise, my temperature sensors are in the ceilings and boiler is 24kw with 7 rads at 55oc.
Scripts run every 30s with the 1Wire refresh.

I think, as it is it works well because if a room requests heat then any other valves open get a top up. If I went with my idea for separate zones and on/off valves there would be individual calls to heat and higher boiler on time (would like to test though if the commandarray worked). This is mainly because I am in a bungalow and have similarish heat loss throughout. In a house I'd have up/down stairs zones.
So, it would all be good IF the StellaZ temperature probe wasn't made in the 'Chocolate Fireguard Factory' these things are all over the place some read nearish others +4.
I'd be happy if the valves stayed reliable enough to know they're good and then I could take the thermocouple out of the valve on a wire to the floor at least.
Also added an Alert Button which displays the zone where heat is needed, It gets overwritten if more than 1 zone needs heat.
If I modify the StellaZ and all is good then this will do, it just needs writing in 1 script with the fancy loop.
Need to add an away button to set all zones to a lower setting automated with PIRs.
Would like to see the Dummy Thermostat with up/down buttons at 0.5 resolution.
Last edited by pdjm43 on Monday 07 November 2016 7:01, edited 1 time in total.
pdjm43
Posts: 30
Joined: Friday 12 December 2014 11:31
Target OS: Raspberry Pi / ODroid
Domoticz version:
Location: UK
Contact:

StellaZ

Post by pdjm43 »

I was just creating a document of steps and commands so that I can rebuild my Pi if I lost the SD and I found this in my bookmarks https://groups.google.com/forum/#!topic ... gNrk58f-Xs
it's a discussion about adding the StellaZ v3 to Zwave with examples.
Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest