app.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. package core
  2. import (
  3. "log"
  4. "time"
  5. "github.com/brutella/hc/accessory"
  6. "github.com/bwdezend/astoria-hc/internal/telemetry"
  7. "github.com/vemo-france/max31865"
  8. )
  9. var p = 3.0
  10. // GetCurrentTemp read from the MAX31865 to read
  11. // the current state of the steam side of the boiler temperature
  12. func GetCurrentTemp(acc accessory.Thermostat) {
  13. if err := max31865.Init(); err != nil {
  14. log.Fatalf("initialization failed : %s", err)
  15. }
  16. sensor := max31865.Create("8", "9", "10", "11")
  17. var boilerTemperature float64
  18. for {
  19. boilerTemperature = float64(sensor.ReadTemperature(100, 430))
  20. if boilerTemperature < 1.0 {
  21. telemetry.SensorFaultCount.Inc()
  22. log.Println("Sensor fault")
  23. } else {
  24. acc.Thermostat.CurrentTemperature.SetValue(boilerTemperature)
  25. telemetry.CurrentTemperature.Set(boilerTemperature)
  26. }
  27. time.Sleep(500 * time.Millisecond)
  28. }
  29. }
  30. // SetTargetTemp adjusts the setpoint for the PID loop and updates the homekit interfaces
  31. func SetTargetTemp(acc accessory.Thermostat, setTemp float64) {
  32. if setTemp > 124.0 {
  33. setTemp = 124.0
  34. }
  35. log.Printf("setting setpoint to %.2f", setTemp)
  36. PersistTemp(setTemp)
  37. acc.Thermostat.TargetTemperature.SetValue(setTemp)
  38. telemetry.SetpointTemperature.Set(setTemp)
  39. }
  40. // HeaterWindow doc Take two inputs - the duration of the cycle and the proportion of the cycle
  41. // and turn the heating element on for that percentage of the cycle. If the
  42. // windowSize is 15.0 and the enabledTime is 0.7, this turns the heating element
  43. // on for 10.5 seconds and off for 4.5 seconds before returning
  44. func HeaterWindow(windowSize float64, enabledTime float64, gpioEnabled bool) {
  45. var disabledTime float64 = 0
  46. enabledTime = windowSize * enabledTime * 1000
  47. disabledTime = windowSize*1000 - enabledTime
  48. if enabledTime > 1000.0 {
  49. enabledTime = 1000.0
  50. }
  51. if enabledTime > 0 {
  52. if gpioEnabled {
  53. HeaterControl(true)
  54. }
  55. time.Sleep(time.Duration(enabledTime) * time.Millisecond)
  56. telemetry.SecondsActive.Add(enabledTime / 1000)
  57. }
  58. if gpioEnabled {
  59. HeaterControl(false)
  60. }
  61. time.Sleep(time.Duration(disabledTime) * time.Millisecond)
  62. }
  63. // TemperatureProportional is a dead simple proportional control loop. Take the difference in setpoint
  64. // and current temp, multiply by the gain, and use the result to control
  65. // the duty cycle on the boiler, represented as a float between 0.0 and 1.0
  66. func TemperatureProportional(acc accessory.Thermostat, gpioEnabled bool) {
  67. for {
  68. current := 0.0
  69. if gpioEnabled {
  70. current = acc.Thermostat.CurrentTemperature.GetValue()
  71. }
  72. setpoint := acc.Thermostat.TargetTemperature.GetValue()
  73. error := (setpoint - current) * p * 0.1
  74. if error > 1.0 {
  75. error = 1.0
  76. }
  77. if error < 0.01 {
  78. error = 0.0
  79. }
  80. // log.Printf("Duty Cycle: %.2f, Current: %.2f, Setpoint: %.2f\n", error, current, setpoint)
  81. HeaterWindow(1.0, error, gpioEnabled)
  82. }
  83. }
  84. // TemperatureErrorDetection is a error handling function to turn the SSR off if the boiler doesn't
  85. // appear to be warming as a result of input. The most common reason for this
  86. // is that the power switch on the machine is turned off, but the timer still
  87. // fired. Less common reasons would be a heater element malfunction, a boiler
  88. // rupture, or a failure of the temperature sensor itself.
  89. func TemperatureErrorDetection(acc accessory.Thermostat) {
  90. lastTemp := 0.0
  91. countdown := 2
  92. for {
  93. setpoint := acc.Thermostat.TargetTemperature.GetValue()
  94. time.Sleep(10 * time.Second)
  95. current := acc.Thermostat.CurrentTemperature.GetValue()
  96. error := (setpoint - current)
  97. if error > 5.0 {
  98. if lastTemp >= current {
  99. if countdown > 0 {
  100. log.Printf("Warning: boiler does not appear to be heating (%.2f gt %.2f). Countdown: %d", lastTemp, current, countdown)
  101. countdown--
  102. } else if countdown == 0 {
  103. log.Printf("Error: boiler does not appear to be heating (%.2f gt %.2f). Changing setpoint to 0.0", lastTemp, current)
  104. SetTargetTemp(acc, 0.0)
  105. countdown = 2
  106. }
  107. }
  108. } else if countdown < 2 {
  109. log.Printf("Warning: boiler does not appear to be heating (%.2f gt %.2f). Countdown: %d", lastTemp, current, countdown)
  110. countdown = 2
  111. }
  112. lastTemp = current
  113. }
  114. }
  115. // Re-evaluate if we want to be able to set the temp from a REST endpoint
  116. // type updateVars struct {
  117. // Temperature float64 `json:"temperature,omitempty"`
  118. // Proportion float64 `json:"proportion,omitempty"`
  119. // }
  120. //
  121. // // UpdateSettings doc
  122. // func UpdateSettings(w http.ResponseWriter, r *http.Request) {
  123. // var update updateVars
  124. // if r.Method == "POST" {
  125. // body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1048576))
  126. // if err != nil {
  127. // http.Error(w, "Error reading request body",
  128. // http.StatusInternalServerError)
  129. // }
  130. // if err := r.Body.Close(); err != nil {
  131. // panic(err)
  132. // }
  133. //
  134. // if err := json.Unmarshal(body, &update); err != nil {
  135. // w.Header().Set("Content-Type", "application/json; charset=UTF-8")
  136. // w.WriteHeader(422) // unprocessable entity
  137. // if err := json.NewEncoder(w).Encode(err); err != nil {
  138. // panic(err)
  139. // }
  140. // }
  141. // if update.Temperature != 0 {
  142. // SetTargetTemp(update.Temperature)
  143. // }
  144. // if update.Proportion != 0 {
  145. // log.Printf("updating p constant to %.2f\n", update.Proportion)
  146. // p = update.Proportion
  147. // }
  148. //
  149. // } else {
  150. // http.Error(w, "Invalid request method", http.StatusMethodNotAllowed)
  151. // }
  152. // }
  153. //
  154. // // SetpointHandler handles
  155. // func SetpointHandler() {
  156. // router := mux.NewRouter().StrictSlash(true)
  157. // router.HandleFunc("/update", UpdateSettings)
  158. // log.Fatal(http.ListenAndServe(":2113", router))
  159. // }