1025 fprintf(stdout, "Finished, rc=%d\n", rc); |
1027 fprintf(stdout, "Finished, rc=%d\n", rc); |
1026 return rc; |
1028 return rc; |
1027 } |
1029 } |
1028 |
1030 |
1029 |
1031 |
|
1032 void do_unit(units_list *unit, int LCDunit, int *seconds, int *minutes) |
|
1033 { |
|
1034 time_t now; |
|
1035 prof_step *step; |
|
1036 int rc, temp; |
|
1037 int run_seconds, run_minutes, run_hours, tot_minutes; |
|
1038 int current_step, valid_step, time_until_now, previous_fridge_mode; |
|
1039 float previous_target_lo, previous_target_hi; |
|
1040 float LCDair, LCDbeer, LCDspL, LCDspH; |
|
1041 unsigned char LCDstatC, LCDstatH; |
|
1042 |
|
1043 unit->mqtt_flag &= ~MQTT_FLAG_DATA; |
|
1044 unit->alarm_flag = 0; |
|
1045 |
|
1046 if (unit->air_address) { |
|
1047 rc = device_in(unit->air_address, &temp); |
|
1048 if (rc == DEVPRESENT_YES) { |
|
1049 if (unit->air_temperature != temp) { |
|
1050 unit->mqtt_flag |= MQTT_FLAG_DATA; |
|
1051 } |
|
1052 unit->air_temperature = temp; |
|
1053 unit->air_state = 0; |
|
1054 } else if (rc == DEVPRESENT_ERROR) { |
|
1055 unit->air_state = 1; |
|
1056 } else { |
|
1057 unit->air_state = 2; |
|
1058 } |
|
1059 } |
|
1060 |
|
1061 if (unit->beer_address) { |
|
1062 rc = device_in(unit->beer_address, &temp); |
|
1063 if ((rc == DEVPRESENT_NO) && unit->beer_address2) { |
|
1064 /* Read alternative sensor */ |
|
1065 rc = device_in(unit->beer_address2, &temp); |
|
1066 } |
|
1067 if (rc == DEVPRESENT_YES) { |
|
1068 if (unit->beer_temperature != temp) { |
|
1069 unit->mqtt_flag |= MQTT_FLAG_DATA; |
|
1070 } |
|
1071 unit->beer_temperature = temp; |
|
1072 unit->beer_state = 0; |
|
1073 } else if (rc == DEVPRESENT_ERROR) { |
|
1074 unit->beer_state = 1; |
|
1075 } else { |
|
1076 unit->beer_state = 2; |
|
1077 } |
|
1078 } |
|
1079 |
|
1080 if (unit->chiller_address) { |
|
1081 rc = device_in(unit->chiller_address, &temp); |
|
1082 if (rc == DEVPRESENT_YES) { |
|
1083 if (unit->chiller_temperature != temp) { |
|
1084 unit->mqtt_flag |= MQTT_FLAG_DATA; |
|
1085 } |
|
1086 unit->chiller_temperature = temp; |
|
1087 unit->chiller_state = 0; |
|
1088 } else if (rc == DEVPRESENT_ERROR) { |
|
1089 unit->chiller_state = 1; |
|
1090 } else { |
|
1091 unit->chiller_state = 2; |
|
1092 } |
|
1093 } |
|
1094 |
|
1095 /* |
|
1096 * Unit door state, default is closed. |
|
1097 */ |
|
1098 if (unit->door_address) { |
|
1099 rc = device_in(unit->door_address, &temp); |
|
1100 if (rc == DEVPRESENT_YES) { |
|
1101 if (temp) { |
|
1102 if (unit->door_state == 0) { |
|
1103 syslog(LOG_NOTICE, "Unit `%s' door closed", unit->alias); |
|
1104 unit->door_state = 1; |
|
1105 unit->mqtt_flag |= MQTT_FLAG_DATA; |
|
1106 } |
|
1107 } else { |
|
1108 if (unit->door_state) { |
|
1109 syslog(LOG_NOTICE, "Unit `%s' door opened", unit->alias); |
|
1110 unit->door_state = 0; |
|
1111 unit->mqtt_flag |= MQTT_FLAG_DATA; |
|
1112 } |
|
1113 /* |
|
1114 * If unit is active and the door is open |
|
1115 */ |
|
1116 if (unit->mode != UNITMODE_NONE) { |
|
1117 unit->alarm_flag |= ALARM_FLAG_DOOR; |
|
1118 } |
|
1119 } |
|
1120 } else { |
|
1121 unit->door_state = 1; |
|
1122 } |
|
1123 } else { |
|
1124 unit->door_state = 1; |
|
1125 } |
|
1126 |
|
1127 /* |
|
1128 * Unit PSU state |
|
1129 */ |
|
1130 if (unit->psu_address) { |
|
1131 rc = device_in(unit->psu_address, &temp); |
|
1132 if (rc == DEVPRESENT_YES) { |
|
1133 if (temp) { |
|
1134 if (unit->psu_state == 0) { |
|
1135 syslog(LOG_NOTICE, "Unit `%s' PSU (12 volt) is on", unit->alias); |
|
1136 unit->psu_state = 1; |
|
1137 unit->mqtt_flag |= MQTT_FLAG_DATA; |
|
1138 } |
|
1139 } else { |
|
1140 if (unit->psu_state) { |
|
1141 syslog(LOG_NOTICE, "Unit `%s' PSU (12 volt) is off", unit->alias); |
|
1142 unit->psu_state = 0; |
|
1143 unit->mqtt_flag |= MQTT_FLAG_DATA; |
|
1144 } |
|
1145 unit->alarm_flag |= ALARM_FLAG_PSU; |
|
1146 } |
|
1147 } else { |
|
1148 unit->psu_state = 1; |
|
1149 } |
|
1150 } else { |
|
1151 /* |
|
1152 * No state available, assume Ok. |
|
1153 */ |
|
1154 unit->psu_state = 1; |
|
1155 } |
|
1156 |
|
1157 /* |
|
1158 * Handle profile |
|
1159 */ |
|
1160 if ((unit->mode == UNITMODE_PROFILE) && (unit->profile_uuid)) { |
|
1161 /* |
|
1162 * unit->prof_started - start time or 0 if not yet running. |
|
1163 * unit->prof_state - PROFILE_OFF|PROFILE_PAUSE|PROFILE_RUN|PROFILE_DONE |
|
1164 * unit->prof_target - Calculated target temperature. |
|
1165 * unit->prof_paused - Internal pause counter. |
|
1166 * unit->prof_peak_abs - Peak temperature of the beer. |
|
1167 * unit->prof_peak_rel - Peak temperature between beer and fridge. |
|
1168 * unit->prof_primary_done - time when primary fermentation was over the peak. |
|
1169 */ |
|
1170 |
|
1171 /* |
|
1172 * Safe defaults |
|
1173 */ |
|
1174 unit->prof_target_lo = unit->profile_inittemp_lo; |
|
1175 unit->prof_target_hi = unit->profile_inittemp_hi; |
|
1176 unit->prof_fridge_mode = 0; |
|
1177 |
|
1178 switch (unit->prof_state) { |
|
1179 case PROFILE_OFF: |
|
1180 unit->prof_percent = 0; |
|
1181 break; |
|
1182 case PROFILE_PAUSE: |
|
1183 /* |
|
1184 * Keep current temperature, measure pause time. For |
|
1185 * temperature fall thru. |
|
1186 */ |
|
1187 unit->prof_paused++; |
|
1188 case PROFILE_RUN: |
|
1189 /* |
|
1190 * Calculate current profile step and desired temperature. |
|
1191 * When all steps are done, set state to PROFILE_DONE. |
|
1192 */ |
|
1193 previous_target_lo = unit->profile_inittemp_lo; |
|
1194 previous_target_hi = unit->profile_inittemp_hi; |
|
1195 previous_fridge_mode = unit->profile_fridge_mode; |
|
1196 time_until_now = current_step = 0; |
|
1197 now = time(NULL); |
|
1198 run_seconds = (int)(now - unit->prof_started - unit->prof_paused); |
|
1199 run_minutes = run_seconds / 60; |
|
1200 run_hours = run_minutes / 60; |
|
1201 if (debug) |
|
1202 fprintf(stdout, "run_HMS=%d,%d,%d ", run_hours, run_minutes, run_seconds); |
|
1203 |
|
1204 /* |
|
1205 * Primary fermentation tests |
|
1206 */ |
|
1207 if ((unit->beer_temperature / 1000.0) > unit->prof_peak_abs) |
|
1208 unit->prof_peak_abs = unit->beer_temperature / 1000.0; |
|
1209 if (((unit->beer_temperature - unit->air_temperature) / 1000.0) > unit->prof_peak_rel) |
|
1210 unit->prof_peak_rel = (unit->beer_temperature - unit->air_temperature) / 1000.0; |
|
1211 if (unit->prof_primary_done == 0) { |
|
1212 if (unit->cooler_address) { |
|
1213 /* |
|
1214 * There is a cooler. If the difference between the beer and air temperature |
|
1215 * drops we assume the primary fermentation is done. |
|
1216 */ |
|
1217 if (((unit->beer_temperature - unit->air_temperature) / 1000.0) < (unit->prof_peak_rel - 0.5)) { |
|
1218 unit->prof_primary_done = time(NULL); |
|
1219 syslog(LOG_NOTICE, "Profile `%s' primary fermentation is ready (cooler mode)", unit->profile_name); |
|
1220 if (! unit->event_msg) |
|
1221 unit->event_msg = xstrcpy((char *)"Primary peak"); |
|
1222 } |
|
1223 } else { |
|
1224 /* |
|
1225 * This method works if the unit has no cooling or if the profile allowed the |
|
1226 * beer temperature to rise freely. |
|
1227 */ |
|
1228 if ((unit->beer_temperature / 1000.0) < (unit->prof_peak_abs - 0.5)) { |
|
1229 unit->prof_primary_done = time(NULL); |
|
1230 syslog(LOG_NOTICE, "Profile `%s' primary fermentation is ready (free rise mode)", unit->profile_name); |
|
1231 if (! unit->event_msg) |
|
1232 unit->event_msg = xstrcpy((char *)"Primary peak"); |
|
1233 } |
|
1234 } |
|
1235 } |
|
1236 |
|
1237 /* |
|
1238 * See how long this profile will take |
|
1239 */ |
|
1240 tot_minutes = 0; |
|
1241 for (step = unit->profile_steps; step; step = step->next) { |
|
1242 tot_minutes += ((step->steptime + step->resttime) * 60); |
|
1243 } |
|
1244 if ((tot_minutes == 0) && unit->profile_totalsteps) { |
|
1245 syslog(LOG_NOTICE, "Profile `%s' steps disappeared", unit->profile_name); |
|
1246 unit->prof_state = PROFILE_OFF; |
|
1247 break; |
|
1248 } |
|
1249 |
|
1250 valid_step = FALSE; |
|
1251 for (step = unit->profile_steps; step; step = step->next) { |
|
1252 /* |
|
1253 * step->steptime |
|
1254 * step->resttime |
|
1255 * step->target |
|
1256 */ |
|
1257 current_step++; |
|
1258 if ((run_hours >= time_until_now) && (run_hours < (time_until_now + step->steptime + step->resttime))) { |
|
1259 /* |
|
1260 * This is our current step |
|
1261 */ |
|
1262 valid_step = TRUE; |
|
1263 if ((run_hours - time_until_now) < step->steptime) { |
|
1264 unit->prof_target_lo = previous_target_lo + (((run_minutes - (time_until_now * 60.0)) / (step->steptime * 60.0)) * (step->target_lo - previous_target_lo)); |
|
1265 unit->prof_target_hi = previous_target_hi + (((run_minutes - (time_until_now * 60.0)) / (step->steptime * 60.0)) * (step->target_hi - previous_target_hi)); |
|
1266 if (step->fridge_mode > previous_fridge_mode) { |
|
1267 unit->prof_fridge_mode = (((run_minutes - (time_until_now * 60)) * 100) / (step->steptime * 60)); |
|
1268 } else if (step->fridge_mode < previous_fridge_mode) { |
|
1269 unit->prof_fridge_mode = 100 - (((run_minutes - (time_until_now * 60)) * 100) / (step->steptime * 60)); |
|
1270 } else { |
|
1271 unit->prof_fridge_mode = step->fridge_mode; |
|
1272 } |
|
1273 if (debug) |
|
1274 fprintf(stdout, "prof_fridge_mode=%d run_minutes=%d steptime=%d time_until_now=%d\n", |
|
1275 unit->prof_fridge_mode, run_minutes, step->steptime, time_until_now); |
|
1276 } else { |
|
1277 unit->prof_target_lo = step->target_lo; |
|
1278 unit->prof_target_hi = step->target_hi; |
|
1279 unit->prof_fridge_mode = step->fridge_mode; |
|
1280 } |
|
1281 break; |
|
1282 } |
|
1283 time_until_now += step->steptime + step->resttime; |
|
1284 previous_target_lo = step->target_lo; |
|
1285 previous_target_hi = step->target_hi; |
|
1286 previous_fridge_mode = step->fridge_mode; |
|
1287 } |
|
1288 |
|
1289 if (valid_step == TRUE) { |
|
1290 unit->prof_percent = (100 * run_minutes) / tot_minutes; |
|
1291 if (((*minutes == 10) || (*minutes == 40)) && (*seconds == 1)) { |
|
1292 syslog(LOG_NOTICE, "Profile `%s' running %dd %02d:%02d in step %d, %d%% done, fridge/beer %d%% %.3f..%.3f degrees", |
|
1293 unit->profile_name, run_hours / 24, run_hours % 24, run_minutes % 60, current_step, |
|
1294 unit->prof_percent, unit->prof_fridge_mode, unit->prof_target_lo, unit->prof_target_hi); |
|
1295 unit->mqtt_flag |= MQTT_FLAG_DATA; |
|
1296 } |
|
1297 } else { |
|
1298 /* |
|
1299 * No more steps to do |
|
1300 */ |
|
1301 unit->prof_state = PROFILE_DONE; |
|
1302 unit->prof_percent = 100; |
|
1303 syslog(LOG_NOTICE, "Profile `%s' is done", unit->profile_name); |
|
1304 unit->mqtt_flag |= MQTT_FLAG_DATA; |
|
1305 if (! unit->event_msg) |
|
1306 unit->event_msg = xstrcpy((char *)"Profile finished"); |
|
1307 } |
|
1308 break; |
|
1309 |
|
1310 case PROFILE_DONE: |
|
1311 /* |
|
1312 * Keep this state, set target temperature to the last step. |
|
1313 */ |
|
1314 previous_target_lo = unit->profile_inittemp_lo; |
|
1315 previous_target_hi = unit->profile_inittemp_hi; |
|
1316 previous_fridge_mode = unit->profile_fridge_mode; |
|
1317 for (step = unit->profile_steps; step; step = step->next) { |
|
1318 if ((step->steptime + step->resttime) == 0) |
|
1319 break; |
|
1320 previous_target_lo = step->target_lo; |
|
1321 previous_target_hi = step->target_hi; |
|
1322 previous_fridge_mode = step->fridge_mode; |
|
1323 } |
|
1324 unit->prof_target_lo = previous_target_lo; |
|
1325 unit->prof_target_hi = previous_target_hi; |
|
1326 unit->prof_fridge_mode = previous_fridge_mode; |
|
1327 unit->prof_percent = 100; |
|
1328 break; |
|
1329 } /* switch */ |
|
1330 } else { |
|
1331 /* |
|
1332 * Set some sane values |
|
1333 */ |
|
1334 unit->prof_target_lo = 19.8; |
|
1335 unit->prof_target_hi = 20.2; |
|
1336 unit->prof_fridge_mode = 0; |
|
1337 } |
|
1338 |
|
1339 /* |
|
1340 * Manual switching |
|
1341 */ |
|
1342 if (unit->mode == UNITMODE_NONE) { |
|
1343 device_out(unit->heater_address, unit->heater_state); |
|
1344 device_out(unit->cooler_address, unit->cooler_state); |
|
1345 device_out(unit->fan_address, unit->fan_state); |
|
1346 } |
|
1347 |
|
1348 /* |
|
1349 * Usage counters |
|
1350 */ |
|
1351 if (unit->heater_address && unit->heater_state) |
|
1352 unit->heater_usage++; |
|
1353 if (unit->cooler_address && unit->cooler_state) |
|
1354 unit->cooler_usage++; |
|
1355 if (unit->fan_address && unit->fan_state) |
|
1356 unit->fan_usage++; |
|
1357 if (unit->light_address && unit->light_state) |
|
1358 unit->light_usage++; |
|
1359 |
|
1360 /* |
|
1361 * Interior lights |
|
1362 */ |
|
1363 if (unit->light_address) { |
|
1364 if (unit->light_timer) { |
|
1365 unit->light_timer--; |
|
1366 } |
|
1367 if (unit->door_state && !unit->light_timer && unit->light_state) { |
|
1368 if (unit->light_wait > 0) { |
|
1369 unit->light_wait--; |
|
1370 } else { |
|
1371 unit->light_state = 0; |
|
1372 syslog(LOG_NOTICE, "Unit `%s' lights On => Off", unit->alias); |
|
1373 unit->mqtt_flag |= MQTT_FLAG_DATA; |
|
1374 } |
|
1375 } |
|
1376 if ((!unit->door_state || unit->light_timer) && !unit->light_state) { |
|
1377 unit->light_wait = unit->light_delay; /* No delay to turn lights on */ |
|
1378 unit->light_state = 1; |
|
1379 unit->mqtt_flag |= MQTT_FLAG_DATA; |
|
1380 syslog(LOG_NOTICE, "Unit `%s' lights Off => On", unit->alias); |
|
1381 } |
|
1382 device_out(unit->light_address, unit->light_state); |
|
1383 } |
|
1384 |
|
1385 /* |
|
1386 * Temperature control in this unit |
|
1387 */ |
|
1388 if ((unit->mode == UNITMODE_FRIDGE) || (unit->mode == UNITMODE_BEER) || (unit->mode == UNITMODE_PROFILE)) { |
|
1389 |
|
1390 /* |
|
1391 * Set both PID's to their input values. |
|
1392 */ |
|
1393 unit->PID_cool->Mode = unit->PID_heat->Mode = PID_MODE_NONE; |
|
1394 if (unit->mode == UNITMODE_FRIDGE) { |
|
1395 unit->PID_cool->SetP = unit->fridge_set_hi; |
|
1396 unit->PID_heat->SetP = unit->fridge_set_lo; |
|
1397 unit->PID_cool->Input = unit->PID_heat->Input = unit->air_temperature / 1000.0; |
|
1398 unit->PID_cool->Mode = unit->PID_heat->Mode = PID_MODE_BOO; |
|
1399 } else if (unit->mode == UNITMODE_BEER) { |
|
1400 unit->PID_cool->SetP = unit->beer_set_hi; |
|
1401 unit->PID_heat->SetP = unit->beer_set_lo; |
|
1402 unit->PID_cool->Input = unit->PID_heat->Input = unit->beer_temperature / 1000.0; |
|
1403 unit->PID_cool->Mode = unit->PID_heat->Mode = PID_MODE_AUTO; |
|
1404 } else if (unit->mode == UNITMODE_PROFILE) { |
|
1405 double usetemp; |
|
1406 unit->PID_cool->SetP = unit->prof_target_hi; |
|
1407 unit->PID_heat->SetP = unit->prof_target_lo; |
|
1408 /* |
|
1409 * Get percentage to use from each thermometer. unit->prof_fridge_mode = 0..100 |
|
1410 */ |
|
1411 usetemp = ((unit->prof_fridge_mode * (unit->air_temperature / 1000.0)) + |
|
1412 ((100 - unit->prof_fridge_mode) * (unit->beer_temperature / 1000.0))) / 100.0; |
|
1413 unit->PID_cool->Input = unit->PID_heat->Input = usetemp; |
|
1414 unit->PID_cool->Mode = unit->PID_heat->Mode = PID_MODE_AUTO; |
|
1415 } |
|
1416 |
|
1417 /* |
|
1418 * PID controller compute, simulate 100 mSec loops by running 10 times. |
|
1419 */ |
|
1420 for (int i = 0; i < 10; i++) { |
|
1421 UpdatePID(unit->PID_heat); |
|
1422 UpdatePID(unit->PID_cool); |
|
1423 } |
|
1424 /* |
|
1425 * Logging |
|
1426 */ |
|
1427 if (unit->heater_address) { |
|
1428 /* |
|
1429 * Prevent extreme heating |
|
1430 */ |
|
1431 if ((unit->mode == UNITMODE_BEER) && ((unit->air_temperature / 1000.0) > (unit->PID_heat->Input + 8.0))) { |
|
1432 unit->PID_heat->OutP = 0.0; |
|
1433 } |
|
1434 if (*seconds == 60) { |
|
1435 syslog(LOG_NOTICE, "Heat: sp=%.3f Input=%.3f iState=%.3f Err=%.3f Out=%.1f", |
|
1436 unit->PID_heat->SetP, unit->PID_heat->Input, unit->PID_heat->iState, unit->PID_heat->Err, unit->PID_heat->OutP); |
|
1437 } |
|
1438 } else { |
|
1439 unit->PID_heat->OutP = 0.0; |
|
1440 } |
|
1441 if (unit->cooler_address) { |
|
1442 /* |
|
1443 * Prevent extreme cooling |
|
1444 */ |
|
1445 if ((unit->mode == UNITMODE_BEER) && ((unit->air_temperature / 1000.0) < (unit->PID_cool->Input - 8.0))) { |
|
1446 unit->PID_cool->OutP = 0.0; |
|
1447 } |
|
1448 /* |
|
1449 * Prevent cooling if we use a chiller and the chiller temperature is not low enough. |
|
1450 */ |
|
1451 if (unit->chiller_address && (unit->chiller_state == 0)) { |
|
1452 if ((unit->chiller_temperature / 1000.0) > ((unit->air_temperature / 1000.0) - 1)) { |
|
1453 unit->PID_cool->OutP = 0.0; |
|
1454 unit->alarm_flag |= ALARM_FLAG_CHILLER; |
|
1455 if (*seconds == 60) { |
|
1456 syslog(LOG_NOTICE, "Cool: Air=%.2f Chiller=%.2f alarm", unit->air_temperature / 1000.0, unit->chiller_temperature / 1000.0); |
|
1457 } |
|
1458 } |
|
1459 } |
|
1460 if (*seconds == 60) { |
|
1461 syslog(LOG_NOTICE, "Cool: sp=%.3f Input=%.3f iState=%.3f Err=%.3f Out=%.1f", |
|
1462 unit->PID_cool->SetP, unit->PID_cool->Input, unit->PID_cool->iState, unit->PID_cool->Err, unit->PID_cool->OutP); |
|
1463 } |
|
1464 } else { |
|
1465 unit->PID_cool->OutP = 0.0; |
|
1466 } |
|
1467 |
|
1468 /* |
|
1469 * Deadlock, kill lowest value. |
|
1470 */ |
|
1471 if (unit->PID_cool->OutP && unit->PID_heat->OutP) { |
|
1472 if (unit->PID_cool->OutP > unit->PID_heat->OutP) |
|
1473 unit->PID_heat->OutP = 0.0; |
|
1474 else |
|
1475 unit->PID_cool->OutP = 0.0; |
|
1476 } |
|
1477 |
|
1478 if (unit->heater_address && ! unit->cooler_state) { |
|
1479 if (unit->PID_heat->OutP >= 50) { |
|
1480 if (unit->heater_wait < unit->heater_delay) { |
|
1481 unit->heater_wait++; |
|
1482 } else { |
|
1483 int power = round(unit->PID_heat->OutP); |
|
1484 if (unit->heater_state != power) { |
|
1485 syslog(LOG_NOTICE, "Unit `%s' heater %d%% => %d%%", unit->alias, unit->heater_state, power); |
|
1486 unit->heater_state = power; |
|
1487 if (unit->heater_address) { |
|
1488 unit->mqtt_flag |= MQTT_FLAG_DATA; |
|
1489 } |
|
1490 } |
|
1491 } |
|
1492 } else { |
|
1493 if (unit->heater_wait > 0) { |
|
1494 unit->heater_wait--; |
|
1495 } else { |
|
1496 if (unit->heater_state) { |
|
1497 syslog(LOG_NOTICE, "Unit `%s' heater On => Off", unit->alias); |
|
1498 unit->heater_state = 0; |
|
1499 if (unit->heater_address) { |
|
1500 unit->mqtt_flag |= MQTT_FLAG_DATA; |
|
1501 } |
|
1502 } |
|
1503 } |
|
1504 } |
|
1505 if (unit->door_state) { |
|
1506 device_out(unit->heater_address, unit->heater_state); |
|
1507 } else { |
|
1508 device_out(unit->heater_address, 0); |
|
1509 } |
|
1510 } |
|
1511 |
|
1512 if (unit->cooler_address && ! unit->heater_state) { |
|
1513 if (unit->PID_cool->OutP >= 50) { |
|
1514 if (unit->cooler_wait < unit->cooler_delay) { |
|
1515 unit->cooler_wait++; |
|
1516 } else { |
|
1517 int power = round(unit->PID_cool->OutP); |
|
1518 if (unit->cooler_state != power) { |
|
1519 syslog(LOG_NOTICE, "Unit `%s' cooler %d%% => %d%%", unit->alias, unit->cooler_state, power); |
|
1520 unit->cooler_state = power; |
|
1521 if (unit->cooler_address) { |
|
1522 unit->mqtt_flag |= MQTT_FLAG_DATA; |
|
1523 } |
|
1524 } |
|
1525 } |
|
1526 } else { |
|
1527 if (unit->cooler_wait > 0) { |
|
1528 unit->cooler_wait--; |
|
1529 } else { |
|
1530 if (unit->cooler_state) { |
|
1531 syslog(LOG_NOTICE, "Unit `%s' cooler On => Off", unit->alias); |
|
1532 unit->cooler_state = 0; |
|
1533 if (unit->cooler_address) { |
|
1534 unit->mqtt_flag |= MQTT_FLAG_DATA; |
|
1535 } |
|
1536 } |
|
1537 } |
|
1538 } |
|
1539 if (unit->door_state) { |
|
1540 device_out(unit->cooler_address, unit->cooler_state); |
|
1541 } else { |
|
1542 device_out(unit->cooler_address, 0); |
|
1543 } |
|
1544 } |
|
1545 |
|
1546 /* |
|
1547 * If there is a fan, and the unit door is closed, and the unit should be doing |
|
1548 * something, then turn on the global fan. |
|
1549 * But if there is a chiller, do not turn it on if cooling. |
|
1550 */ |
|
1551 if (unit->fan_address) { |
|
1552 if ((unit->door_state) && (unit->cooler_state == 0)) { |
|
1553 if (unit->fan_wait < unit->fan_delay) { |
|
1554 unit->fan_wait++; |
|
1555 } else { |
|
1556 if (! unit->fan_state) { |
|
1557 syslog(LOG_NOTICE, "Unit `%s' Fan Off => On", unit->alias); |
|
1558 unit->fan_state = 100; |
|
1559 if (unit->fan_address) { |
|
1560 unit->mqtt_flag |= MQTT_FLAG_DATA; |
|
1561 } |
|
1562 } |
|
1563 } |
|
1564 } else { |
|
1565 if (unit->fan_wait > 0) { |
|
1566 unit->fan_wait--; |
|
1567 } else { |
|
1568 if (unit->fan_state) { |
|
1569 syslog(LOG_NOTICE, "Unit `%s' Fan On => Off", unit->alias); |
|
1570 unit->fan_state = 0; |
|
1571 if (unit->fan_address) { |
|
1572 unit->mqtt_flag |= MQTT_FLAG_DATA; |
|
1573 } |
|
1574 } |
|
1575 } |
|
1576 } |
|
1577 device_out(unit->fan_address, unit->fan_state); |
|
1578 } |
|
1579 |
|
1580 } else { |
|
1581 unit->PID_cool->Mode = unit->PID_heat->Mode = PID_MODE_NONE; |
|
1582 } /* fridge beer or profile mode */ |
|
1583 |
|
1584 /* |
|
1585 * Now everything is set and done, update the LCD display |
|
1586 */ |
|
1587 LCDair = unit->air_temperature / 1000.0; |
|
1588 LCDbeer = unit->beer_temperature / 1000.0; |
|
1589 LCDstatC = LCDstatH = ' '; |
|
1590 if (unit->heater_address) { |
|
1591 if (unit->heater_state) |
|
1592 LCDstatH = '\6'; |
|
1593 else |
|
1594 LCDstatH = '\5'; |
|
1595 } |
|
1596 if (unit->cooler_address) { |
|
1597 if (unit->cooler_state) |
|
1598 LCDstatC = '\4'; |
|
1599 else |
|
1600 LCDstatC = '\3'; |
|
1601 } |
|
1602 LCDspH = LCDspL = 0.0; |
|
1603 if (unit->mode == UNITMODE_BEER) { |
|
1604 LCDspH = unit->beer_set_hi; |
|
1605 LCDspL = unit->beer_set_lo; |
|
1606 } else if (unit->mode == UNITMODE_FRIDGE) { |
|
1607 LCDspH = unit->fridge_set_hi; |
|
1608 LCDspL = unit->fridge_set_lo; |
|
1609 } else if (unit->mode == UNITMODE_PROFILE) { |
|
1610 if (unit->prof_state != PROFILE_OFF) { |
|
1611 LCDspL = unit->prof_target_lo; |
|
1612 LCDspH = unit->prof_target_hi; |
|
1613 } |
|
1614 } |
|
1615 if (*seconds == 60) { |
|
1616 unit->mqtt_flag |= MQTT_FLAG_DATA; |
|
1617 } |
|
1618 pthread_mutex_lock(&mutexes[LOCK_LCD]); |
|
1619 /* |
|
1620 * Write 4 rows to the LCD to display the unit state |
|
1621 */ |
|
1622 lcd_buf_write(row++, "Unit %d: %s ", LCDunit, UNITMODE[unit->mode]); |
|
1623 lcd_buf_write(row++, "%s ", unit->product_name); |
|
1624 lcd_buf_write(row++, "%c%5.1f\2 A%6.2f\1 ", LCDstatC, LCDspH, LCDair); |
|
1625 lcd_buf_write(row++, "%c%5.1f\2 B%6.2f\1 ", LCDstatH, LCDspL, LCDbeer); |
|
1626 pthread_mutex_unlock(&mutexes[LOCK_LCD]); |
|
1627 |
|
1628 /* |
|
1629 * Publish MQTT messages set in flag |
|
1630 */ |
|
1631 if (unit->mqtt_flag) { |
|
1632 if (unit->mqtt_flag & MQTT_FLAG_BIRTH) { |
|
1633 publishDBirth(unit); |
|
1634 unit->mqtt_flag &= ~MQTT_FLAG_BIRTH; |
|
1635 } else { |
|
1636 publishDData(unit); |
|
1637 unit->mqtt_flag &= ~MQTT_FLAG_DATA; |
|
1638 } |
|
1639 if (unit->mqtt_flag & MQTT_FLAG_DEATH) { |
|
1640 publishDDeath(unit); |
|
1641 unit->mqtt_flag &= ~MQTT_FLAG_DEATH; |
|
1642 } |
|
1643 } |
|
1644 |
|
1645 /* |
|
1646 * Handle changed alarms |
|
1647 */ |
|
1648 if (unit->alarm_flag != unit->alarm_last) { |
|
1649 syslog(LOG_NOTICE, "Unit `%s' Alarm %d => %d", unit->alias, unit->alarm_last, unit->alarm_flag); |
|
1650 unit->alarm_last = unit->alarm_flag; |
|
1651 } |
|
1652 } |
|
1653 |
|
1654 |
1030 |
1655 |
1031 int server(void) |
1656 int server(void) |
1032 { |
1657 { |
1033 time_t now, last = (time_t)0, ndata = (time_t)0;; |
1658 time_t now, last = (time_t)0, ndata = (time_t)0;; |
1034 units_list *unit; |
1659 units_list *unit; |
1035 prof_step *step; |
1660 int rc, run = 1, seconds = 0, minutes = 0, temp; |
1036 int row, rc, run = 1, seconds = 0, minutes = 0, temp; |
1661 int key; |
1037 int run_seconds, run_minutes, run_hours, tot_minutes, key; |
|
1038 struct tm *tm; |
1662 struct tm *tm; |
1039 long t = 0; |
1663 long t = 0; |
1040 int current_step, valid_step, time_until_now, previous_fridge_mode; |
|
1041 float previous_target_lo, previous_target_hi; |
|
1042 float LCDair, LCDbeer, LCDspL, LCDspH; |
|
1043 unsigned char LCDstatC, LCDstatH; |
|
1044 int LCDunit; |
1664 int LCDunit; |
1045 |
1665 |
1046 syslog(LOG_NOTICE, "Server process started"); |
1666 syslog(LOG_NOTICE, "Server process started"); |
1047 my_shutdown = my_reboot = FALSE; |
1667 my_shutdown = my_reboot = FALSE; |
1048 if (lockprog((char *)"thermferm")) { |
1668 if (lockprog((char *)"thermferm")) { |
1243 } |
1863 } |
1244 |
1864 |
1245 LCDunit = 0; |
1865 LCDunit = 0; |
1246 for (unit = Config.units; unit; unit = unit->next) { |
1866 for (unit = Config.units; unit; unit = unit->next) { |
1247 LCDunit++; |
1867 LCDunit++; |
1248 unit->mqtt_flag &= ~MQTT_FLAG_DATA; |
1868 do_unit(unit, LCDunit, &seconds, &minutes); |
1249 unit->alarm_flag = 0; |
1869 |
1250 |
|
1251 if (unit->air_address) { |
|
1252 rc = device_in(unit->air_address, &temp); |
|
1253 if (rc == DEVPRESENT_YES) { |
|
1254 if (unit->air_temperature != temp) { |
|
1255 unit->mqtt_flag |= MQTT_FLAG_DATA; |
|
1256 } |
|
1257 unit->air_temperature = temp; |
|
1258 unit->air_state = 0; |
|
1259 } else if (rc == DEVPRESENT_ERROR) { |
|
1260 unit->air_state = 1; |
|
1261 } else { |
|
1262 unit->air_state = 2; |
|
1263 } |
|
1264 } |
|
1265 |
|
1266 if (unit->beer_address) { |
|
1267 rc = device_in(unit->beer_address, &temp); |
|
1268 if ((rc == DEVPRESENT_NO) && unit->beer_address2) { |
|
1269 /* Read alternative sensor */ |
|
1270 rc = device_in(unit->beer_address2, &temp); |
|
1271 } |
|
1272 if (rc == DEVPRESENT_YES) { |
|
1273 if (unit->beer_temperature != temp) { |
|
1274 unit->mqtt_flag |= MQTT_FLAG_DATA; |
|
1275 } |
|
1276 unit->beer_temperature = temp; |
|
1277 unit->beer_state = 0; |
|
1278 } else if (rc == DEVPRESENT_ERROR) { |
|
1279 unit->beer_state = 1; |
|
1280 } else { |
|
1281 unit->beer_state = 2; |
|
1282 } |
|
1283 } |
|
1284 |
|
1285 if (unit->chiller_address) { |
|
1286 rc = device_in(unit->chiller_address, &temp); |
|
1287 if (rc == DEVPRESENT_YES) { |
|
1288 if (unit->chiller_temperature != temp) { |
|
1289 unit->mqtt_flag |= MQTT_FLAG_DATA; |
|
1290 } |
|
1291 unit->chiller_temperature = temp; |
|
1292 unit->chiller_state = 0; |
|
1293 } else if (rc == DEVPRESENT_ERROR) { |
|
1294 unit->chiller_state = 1; |
|
1295 } else { |
|
1296 unit->chiller_state = 2; |
|
1297 } |
|
1298 } |
|
1299 |
|
1300 /* |
|
1301 * Unit door state, default is closed. |
|
1302 */ |
|
1303 if (unit->door_address) { |
|
1304 rc = device_in(unit->door_address, &temp); |
|
1305 if (rc == DEVPRESENT_YES) { |
|
1306 if (temp) { |
|
1307 if (unit->door_state == 0) { |
|
1308 syslog(LOG_NOTICE, "Unit `%s' door closed", unit->alias); |
|
1309 unit->door_state = 1; |
|
1310 unit->mqtt_flag |= MQTT_FLAG_DATA; |
|
1311 } |
|
1312 } else { |
|
1313 if (unit->door_state) { |
|
1314 syslog(LOG_NOTICE, "Unit `%s' door opened", unit->alias); |
|
1315 unit->door_state = 0; |
|
1316 unit->mqtt_flag |= MQTT_FLAG_DATA; |
|
1317 } |
|
1318 /* |
|
1319 * If unit is active and the door is open |
|
1320 */ |
|
1321 if (unit->mode != UNITMODE_NONE) { |
|
1322 unit->alarm_flag |= ALARM_FLAG_DOOR; |
|
1323 } |
|
1324 } |
|
1325 } else { |
|
1326 unit->door_state = 1; |
|
1327 } |
|
1328 } else { |
|
1329 unit->door_state = 1; |
|
1330 } |
|
1331 |
|
1332 /* |
|
1333 * Unit PSU state |
|
1334 */ |
|
1335 if (unit->psu_address) { |
|
1336 rc = device_in(unit->psu_address, &temp); |
|
1337 if (rc == DEVPRESENT_YES) { |
|
1338 if (temp) { |
|
1339 if (unit->psu_state == 0) { |
|
1340 syslog(LOG_NOTICE, "Unit `%s' PSU (12 volt) is on", unit->alias); |
|
1341 unit->psu_state = 1; |
|
1342 unit->mqtt_flag |= MQTT_FLAG_DATA; |
|
1343 } |
|
1344 } else { |
|
1345 if (unit->psu_state) { |
|
1346 syslog(LOG_NOTICE, "Unit `%s' PSU (12 volt) is off", unit->alias); |
|
1347 unit->psu_state = 0; |
|
1348 unit->mqtt_flag |= MQTT_FLAG_DATA; |
|
1349 } |
|
1350 unit->alarm_flag |= ALARM_FLAG_PSU; |
|
1351 } |
|
1352 } else { |
|
1353 unit->psu_state = 1; |
|
1354 } |
|
1355 } else { |
|
1356 /* |
|
1357 * No state available, assume Ok. |
|
1358 */ |
|
1359 unit->psu_state = 1; |
|
1360 } |
|
1361 |
|
1362 /* |
|
1363 * Handle profile |
|
1364 */ |
|
1365 if ((unit->mode == UNITMODE_PROFILE) && (unit->profile_uuid)) { |
|
1366 /* |
|
1367 * unit->prof_started - start time or 0 if not yet running. |
|
1368 * unit->prof_state - PROFILE_OFF|PROFILE_PAUSE|PROFILE_RUN|PROFILE_DONE |
|
1369 * unit->prof_target - Calculated target temperature. |
|
1370 * unit->prof_paused - Internal pause counter. |
|
1371 * unit->prof_peak_abs - Peak temperature of the beer. |
|
1372 * unit->prof_peak_rel - Peak temperature between beer and fridge. |
|
1373 * unit->prof_primary_done - time when primary fermentation was over the peak. |
|
1374 */ |
|
1375 |
|
1376 /* |
|
1377 * Safe defaults |
|
1378 */ |
|
1379 unit->prof_target_lo = unit->profile_inittemp_lo; |
|
1380 unit->prof_target_hi = unit->profile_inittemp_hi; |
|
1381 unit->prof_fridge_mode = 0; |
|
1382 |
|
1383 switch (unit->prof_state) { |
|
1384 case PROFILE_OFF: |
|
1385 unit->prof_percent = 0; |
|
1386 break; |
|
1387 case PROFILE_PAUSE: |
|
1388 /* |
|
1389 * Keep current temperature, measure pause time. For |
|
1390 * temperature fall thru. |
|
1391 */ |
|
1392 unit->prof_paused++; |
|
1393 case PROFILE_RUN: |
|
1394 /* |
|
1395 * Calculate current profile step and desired temperature. |
|
1396 * When all steps are done, set state to PROFILE_DONE. |
|
1397 */ |
|
1398 previous_target_lo = unit->profile_inittemp_lo; |
|
1399 previous_target_hi = unit->profile_inittemp_hi; |
|
1400 previous_fridge_mode = unit->profile_fridge_mode; |
|
1401 time_until_now = current_step = 0; |
|
1402 run_seconds = (int)(now - unit->prof_started - unit->prof_paused); |
|
1403 run_minutes = run_seconds / 60; |
|
1404 run_hours = run_minutes / 60; |
|
1405 if (debug) |
|
1406 fprintf(stdout, "run_HMS=%d,%d,%d ", run_hours, run_minutes, run_seconds); |
|
1407 |
|
1408 /* |
|
1409 * Primary fermentation tests |
|
1410 */ |
|
1411 if ((unit->beer_temperature / 1000.0) > unit->prof_peak_abs) |
|
1412 unit->prof_peak_abs = unit->beer_temperature / 1000.0; |
|
1413 if (((unit->beer_temperature - unit->air_temperature) / 1000.0) > unit->prof_peak_rel) |
|
1414 unit->prof_peak_rel = (unit->beer_temperature - unit->air_temperature) / 1000.0; |
|
1415 if (unit->prof_primary_done == 0) { |
|
1416 if (unit->cooler_address) { |
|
1417 /* |
|
1418 * There is a cooler. If the difference between the beer and air temperature |
|
1419 * drops we assume the primary fermentation is done. |
|
1420 */ |
|
1421 if (((unit->beer_temperature - unit->air_temperature) / 1000.0) < (unit->prof_peak_rel - 0.5)) { |
|
1422 unit->prof_primary_done = time(NULL); |
|
1423 syslog(LOG_NOTICE, "Profile `%s' primary fermentation is ready (cooler mode)", unit->profile_name); |
|
1424 if (! unit->event_msg) |
|
1425 unit->event_msg = xstrcpy((char *)"Primary peak"); |
|
1426 } |
|
1427 } else { |
|
1428 /* |
|
1429 * This method works if the unit has no cooling or if the profile allowed the |
|
1430 * beer temperature to rise freely. |
|
1431 */ |
|
1432 if ((unit->beer_temperature / 1000.0) < (unit->prof_peak_abs - 0.5)) { |
|
1433 unit->prof_primary_done = time(NULL); |
|
1434 syslog(LOG_NOTICE, "Profile `%s' primary fermentation is ready (free rise mode)", unit->profile_name); |
|
1435 if (! unit->event_msg) |
|
1436 unit->event_msg = xstrcpy((char *)"Primary peak"); |
|
1437 } |
|
1438 } |
|
1439 } |
|
1440 |
|
1441 /* |
|
1442 * See how long this profile will take |
|
1443 */ |
|
1444 tot_minutes = 0; |
|
1445 for (step = unit->profile_steps; step; step = step->next) { |
|
1446 tot_minutes += ((step->steptime + step->resttime) * 60); |
|
1447 } |
|
1448 if ((tot_minutes == 0) && unit->profile_totalsteps) { |
|
1449 syslog(LOG_NOTICE, "Profile `%s' steps disappeared", unit->profile_name); |
|
1450 unit->prof_state = PROFILE_OFF; |
|
1451 break; |
|
1452 } |
|
1453 |
|
1454 valid_step = FALSE; |
|
1455 for (step = unit->profile_steps; step; step = step->next) { |
|
1456 /* |
|
1457 * step->steptime |
|
1458 * step->resttime |
|
1459 * step->target |
|
1460 */ |
|
1461 current_step++; |
|
1462 if ((run_hours >= time_until_now) && (run_hours < (time_until_now + step->steptime + step->resttime))) { |
|
1463 /* |
|
1464 * This is our current step |
|
1465 */ |
|
1466 valid_step = TRUE; |
|
1467 if ((run_hours - time_until_now) < step->steptime) { |
|
1468 unit->prof_target_lo = previous_target_lo + (((run_minutes - (time_until_now * 60.0)) / (step->steptime * 60.0)) * (step->target_lo - previous_target_lo)); |
|
1469 unit->prof_target_hi = previous_target_hi + (((run_minutes - (time_until_now * 60.0)) / (step->steptime * 60.0)) * (step->target_hi - previous_target_hi)); |
|
1470 if (step->fridge_mode > previous_fridge_mode) { |
|
1471 unit->prof_fridge_mode = (((run_minutes - (time_until_now * 60)) * 100) / (step->steptime * 60)); |
|
1472 } else if (step->fridge_mode < previous_fridge_mode) { |
|
1473 unit->prof_fridge_mode = 100 - (((run_minutes - (time_until_now * 60)) * 100) / (step->steptime * 60)); |
|
1474 } else { |
|
1475 unit->prof_fridge_mode = step->fridge_mode; |
|
1476 } |
|
1477 if (debug) |
|
1478 fprintf(stdout, "prof_fridge_mode=%d run_minutes=%d steptime=%d time_until_now=%d\n", |
|
1479 unit->prof_fridge_mode, run_minutes, step->steptime, time_until_now); |
|
1480 } else { |
|
1481 unit->prof_target_lo = step->target_lo; |
|
1482 unit->prof_target_hi = step->target_hi; |
|
1483 unit->prof_fridge_mode = step->fridge_mode; |
|
1484 } |
|
1485 break; |
|
1486 } |
|
1487 time_until_now += step->steptime + step->resttime; |
|
1488 previous_target_lo = step->target_lo; |
|
1489 previous_target_hi = step->target_hi; |
|
1490 previous_fridge_mode = step->fridge_mode; |
|
1491 } |
|
1492 |
|
1493 if (valid_step == TRUE) { |
|
1494 unit->prof_percent = (100 * run_minutes) / tot_minutes; |
|
1495 if (((minutes == 10) || (minutes == 40)) && (seconds == 1)) { |
|
1496 syslog(LOG_NOTICE, "Profile `%s' running %dd %02d:%02d in step %d, %d%% done, fridge/beer %d%% %.3f..%.3f degrees", |
|
1497 unit->profile_name, run_hours / 24, run_hours % 24, run_minutes % 60, current_step, |
|
1498 unit->prof_percent, unit->prof_fridge_mode, unit->prof_target_lo, unit->prof_target_hi); |
|
1499 unit->mqtt_flag |= MQTT_FLAG_DATA; |
|
1500 } |
|
1501 } else { |
|
1502 /* |
|
1503 * No more steps to do |
|
1504 */ |
|
1505 unit->prof_state = PROFILE_DONE; |
|
1506 unit->prof_percent = 100; |
|
1507 syslog(LOG_NOTICE, "Profile `%s' is done", unit->profile_name); |
|
1508 unit->mqtt_flag |= MQTT_FLAG_DATA; |
|
1509 if (! unit->event_msg) |
|
1510 unit->event_msg = xstrcpy((char *)"Profile finished"); |
|
1511 } |
|
1512 break; |
|
1513 |
|
1514 case PROFILE_DONE: |
|
1515 /* |
|
1516 * Keep this state, set target temperature to the last step. |
|
1517 */ |
|
1518 previous_target_lo = unit->profile_inittemp_lo; |
|
1519 previous_target_hi = unit->profile_inittemp_hi; |
|
1520 previous_fridge_mode = unit->profile_fridge_mode; |
|
1521 for (step = unit->profile_steps; step; step = step->next) { |
|
1522 if ((step->steptime + step->resttime) == 0) |
|
1523 break; |
|
1524 previous_target_lo = step->target_lo; |
|
1525 previous_target_hi = step->target_hi; |
|
1526 previous_fridge_mode = step->fridge_mode; |
|
1527 } |
|
1528 unit->prof_target_lo = previous_target_lo; |
|
1529 unit->prof_target_hi = previous_target_hi; |
|
1530 unit->prof_fridge_mode = previous_fridge_mode; |
|
1531 unit->prof_percent = 100; |
|
1532 break; |
|
1533 } /* switch */ |
|
1534 } else { |
|
1535 /* |
|
1536 * Set some sane values |
|
1537 */ |
|
1538 unit->prof_target_lo = 19.8; |
|
1539 unit->prof_target_hi = 20.2; |
|
1540 unit->prof_fridge_mode = 0; |
|
1541 } |
|
1542 |
|
1543 /* |
|
1544 * Manual switching |
|
1545 */ |
|
1546 if (unit->mode == UNITMODE_NONE) { |
|
1547 device_out(unit->heater_address, unit->heater_state); |
|
1548 device_out(unit->cooler_address, unit->cooler_state); |
|
1549 device_out(unit->fan_address, unit->fan_state); |
|
1550 } |
|
1551 |
|
1552 /* |
|
1553 * Usage counters |
|
1554 */ |
|
1555 if (unit->heater_address && unit->heater_state) |
|
1556 unit->heater_usage++; |
|
1557 if (unit->cooler_address && unit->cooler_state) |
|
1558 unit->cooler_usage++; |
|
1559 if (unit->fan_address && unit->fan_state) |
|
1560 unit->fan_usage++; |
|
1561 if (unit->light_address && unit->light_state) |
|
1562 unit->light_usage++; |
|
1563 |
|
1564 /* |
|
1565 * Interior lights |
|
1566 */ |
|
1567 if (unit->light_address) { |
|
1568 if (unit->light_timer) { |
|
1569 unit->light_timer--; |
|
1570 } |
|
1571 if (unit->door_state && !unit->light_timer && unit->light_state) { |
|
1572 if (unit->light_wait > 0) { |
|
1573 unit->light_wait--; |
|
1574 } else { |
|
1575 unit->light_state = 0; |
|
1576 syslog(LOG_NOTICE, "Unit `%s' lights On => Off", unit->alias); |
|
1577 unit->mqtt_flag |= MQTT_FLAG_DATA; |
|
1578 } |
|
1579 } |
|
1580 if ((!unit->door_state || unit->light_timer) && !unit->light_state) { |
|
1581 unit->light_wait = unit->light_delay; /* No delay to turn lights on */ |
|
1582 unit->light_state = 1; |
|
1583 unit->mqtt_flag |= MQTT_FLAG_DATA; |
|
1584 syslog(LOG_NOTICE, "Unit `%s' lights Off => On", unit->alias); |
|
1585 } |
|
1586 device_out(unit->light_address, unit->light_state); |
|
1587 } |
|
1588 |
|
1589 /* |
|
1590 * Temperature control in this unit |
|
1591 */ |
|
1592 if ((unit->mode == UNITMODE_FRIDGE) || (unit->mode == UNITMODE_BEER) || (unit->mode == UNITMODE_PROFILE)) { |
|
1593 |
|
1594 /* |
|
1595 * Set both PID's to their input values. |
|
1596 */ |
|
1597 unit->PID_cool->Mode = unit->PID_heat->Mode = PID_MODE_NONE; |
|
1598 if (unit->mode == UNITMODE_FRIDGE) { |
|
1599 unit->PID_cool->SetP = unit->fridge_set_hi; |
|
1600 unit->PID_heat->SetP = unit->fridge_set_lo; |
|
1601 unit->PID_cool->Input = unit->PID_heat->Input = unit->air_temperature / 1000.0; |
|
1602 unit->PID_cool->Mode = unit->PID_heat->Mode = PID_MODE_BOO; |
|
1603 } else if (unit->mode == UNITMODE_BEER) { |
|
1604 unit->PID_cool->SetP = unit->beer_set_hi; |
|
1605 unit->PID_heat->SetP = unit->beer_set_lo; |
|
1606 unit->PID_cool->Input = unit->PID_heat->Input = unit->beer_temperature / 1000.0; |
|
1607 unit->PID_cool->Mode = unit->PID_heat->Mode = PID_MODE_AUTO; |
|
1608 } else if (unit->mode == UNITMODE_PROFILE) { |
|
1609 double usetemp; |
|
1610 unit->PID_cool->SetP = unit->prof_target_hi; |
|
1611 unit->PID_heat->SetP = unit->prof_target_lo; |
|
1612 /* |
|
1613 * Get percentage to use from each thermometer. unit->prof_fridge_mode = 0..100 |
|
1614 */ |
|
1615 usetemp = ((unit->prof_fridge_mode * (unit->air_temperature / 1000.0)) + |
|
1616 ((100 - unit->prof_fridge_mode) * (unit->beer_temperature / 1000.0))) / 100.0; |
|
1617 unit->PID_cool->Input = unit->PID_heat->Input = usetemp; |
|
1618 unit->PID_cool->Mode = unit->PID_heat->Mode = PID_MODE_AUTO; |
|
1619 } |
|
1620 |
|
1621 /* |
|
1622 * PID controller compute, simulate 100 mSec loops. |
|
1623 */ |
|
1624 for (int i = 0; i < 10; i++) { |
|
1625 UpdatePID(unit->PID_heat); |
|
1626 UpdatePID(unit->PID_cool); |
|
1627 } |
|
1628 |
|
1629 /* |
|
1630 * Logging |
|
1631 */ |
|
1632 if (unit->heater_address) { |
|
1633 /* |
|
1634 * Prevent extreme heating |
|
1635 */ |
|
1636 if ((unit->mode == UNITMODE_BEER) && ((unit->air_temperature / 1000.0) > (unit->PID_heat->Input + 8.0))) { |
|
1637 unit->PID_heat->OutP = 0.0; |
|
1638 } |
|
1639 if (seconds == 60) { |
|
1640 syslog(LOG_NOTICE, "Heat: sp=%.3f Input=%.3f iState=%.3f Err=%.3f Out=%.1f", |
|
1641 unit->PID_heat->SetP, unit->PID_heat->Input, unit->PID_heat->iState, unit->PID_heat->Err, unit->PID_heat->OutP); |
|
1642 } |
|
1643 } else { |
|
1644 unit->PID_heat->OutP = 0.0; |
|
1645 } |
|
1646 if (unit->cooler_address) { |
|
1647 /* |
|
1648 * Prevent extreme cooling |
|
1649 */ |
|
1650 if ((unit->mode == UNITMODE_BEER) && ((unit->air_temperature / 1000.0) < (unit->PID_cool->Input - 8.0))) { |
|
1651 unit->PID_cool->OutP = 0.0; |
|
1652 } |
|
1653 /* |
|
1654 * Prevent cooling if we use a chiller and the chiller temperature is not low enough. |
|
1655 */ |
|
1656 if (unit->chiller_address && (unit->chiller_state == 0)) { |
|
1657 if ((unit->chiller_temperature / 1000.0) > ((unit->air_temperature / 1000.0) - 1)) { |
|
1658 unit->PID_cool->OutP = 0.0; |
|
1659 unit->alarm_flag |= ALARM_FLAG_CHILLER; |
|
1660 if (seconds == 60) { |
|
1661 syslog(LOG_NOTICE, "Cool: Air=%.2f Chiller=%.2f alarm", unit->air_temperature / 1000.0, unit->chiller_temperature / 1000.0); |
|
1662 } |
|
1663 } |
|
1664 } |
|
1665 if (seconds == 60) { |
|
1666 syslog(LOG_NOTICE, "Cool: sp=%.3f Input=%.3f iState=%.3f Err=%.3f Out=%.1f", |
|
1667 unit->PID_cool->SetP, unit->PID_cool->Input, unit->PID_cool->iState, unit->PID_cool->Err, unit->PID_cool->OutP); |
|
1668 } |
|
1669 } else { |
|
1670 unit->PID_cool->OutP = 0.0; |
|
1671 } |
|
1672 |
|
1673 /* |
|
1674 * Deadlock, kill lowest value. |
|
1675 */ |
|
1676 if (unit->PID_cool->OutP && unit->PID_heat->OutP) { |
|
1677 if (unit->PID_cool->OutP > unit->PID_heat->OutP) |
|
1678 unit->PID_heat->OutP = 0.0; |
|
1679 else |
|
1680 unit->PID_cool->OutP = 0.0; |
|
1681 } |
|
1682 |
|
1683 if (unit->heater_address && ! unit->cooler_state) { |
|
1684 if (unit->PID_heat->OutP >= 50) { |
|
1685 if (unit->heater_wait < unit->heater_delay) { |
|
1686 unit->heater_wait++; |
|
1687 } else { |
|
1688 int power = round(unit->PID_heat->OutP); |
|
1689 if (unit->heater_state != power) { |
|
1690 syslog(LOG_NOTICE, "Unit `%s' heater %d%% => %d%%", unit->alias, unit->heater_state, power); |
|
1691 unit->heater_state = power; |
|
1692 if (unit->heater_address) { |
|
1693 unit->mqtt_flag |= MQTT_FLAG_DATA; |
|
1694 } |
|
1695 } |
|
1696 } |
|
1697 } else { |
|
1698 if (unit->heater_wait > 0) { |
|
1699 unit->heater_wait--; |
|
1700 } else { |
|
1701 if (unit->heater_state) { |
|
1702 syslog(LOG_NOTICE, "Unit `%s' heater On => Off", unit->alias); |
|
1703 unit->heater_state = 0; |
|
1704 if (unit->heater_address) { |
|
1705 unit->mqtt_flag |= MQTT_FLAG_DATA; |
|
1706 } |
|
1707 } |
|
1708 } |
|
1709 } |
|
1710 if (unit->door_state) { |
|
1711 device_out(unit->heater_address, unit->heater_state); |
|
1712 } else { |
|
1713 device_out(unit->heater_address, 0); |
|
1714 } |
|
1715 } |
|
1716 |
|
1717 if (unit->cooler_address && ! unit->heater_state) { |
|
1718 if (unit->PID_cool->OutP >= 50) { |
|
1719 if (unit->cooler_wait < unit->cooler_delay) { |
|
1720 unit->cooler_wait++; |
|
1721 } else { |
|
1722 int power = round(unit->PID_cool->OutP); |
|
1723 if (unit->cooler_state != power) { |
|
1724 syslog(LOG_NOTICE, "Unit `%s' cooler %d%% => %d%%", unit->alias, unit->cooler_state, power); |
|
1725 unit->cooler_state = power; |
|
1726 if (unit->cooler_address) { |
|
1727 unit->mqtt_flag |= MQTT_FLAG_DATA; |
|
1728 } |
|
1729 } |
|
1730 } |
|
1731 } else { |
|
1732 if (unit->cooler_wait > 0) { |
|
1733 unit->cooler_wait--; |
|
1734 } else { |
|
1735 if (unit->cooler_state) { |
|
1736 syslog(LOG_NOTICE, "Unit `%s' cooler On => Off", unit->alias); |
|
1737 unit->cooler_state = 0; |
|
1738 if (unit->cooler_address) { |
|
1739 unit->mqtt_flag |= MQTT_FLAG_DATA; |
|
1740 } |
|
1741 } |
|
1742 } |
|
1743 } |
|
1744 if (unit->door_state) { |
|
1745 device_out(unit->cooler_address, unit->cooler_state); |
|
1746 } else { |
|
1747 device_out(unit->cooler_address, 0); |
|
1748 } |
|
1749 } |
|
1750 |
|
1751 /* |
|
1752 * If there is a fan, and the unit door is closed, and the unit should be doing |
|
1753 * something, then turn on the global fan. |
|
1754 * But if there is a chiller, do not turn it on if cooling. |
|
1755 */ |
|
1756 if (unit->fan_address) { |
|
1757 if ((unit->door_state) && (unit->cooler_state == 0)) { |
|
1758 if (unit->fan_wait < unit->fan_delay) { |
|
1759 unit->fan_wait++; |
|
1760 } else { |
|
1761 if (! unit->fan_state) { |
|
1762 syslog(LOG_NOTICE, "Unit `%s' Fan Off => On", unit->alias); |
|
1763 unit->fan_state = 100; |
|
1764 if (unit->fan_address) { |
|
1765 unit->mqtt_flag |= MQTT_FLAG_DATA; |
|
1766 } |
|
1767 } |
|
1768 } |
|
1769 } else { |
|
1770 if (unit->fan_wait > 0) { |
|
1771 unit->fan_wait--; |
|
1772 } else { |
|
1773 if (unit->fan_state) { |
|
1774 syslog(LOG_NOTICE, "Unit `%s' Fan On => Off", unit->alias); |
|
1775 unit->fan_state = 0; |
|
1776 if (unit->fan_address) { |
|
1777 unit->mqtt_flag |= MQTT_FLAG_DATA; |
|
1778 } |
|
1779 } |
|
1780 } |
|
1781 } |
|
1782 device_out(unit->fan_address, unit->fan_state); |
|
1783 } |
|
1784 |
|
1785 } else { |
|
1786 unit->PID_cool->Mode = unit->PID_heat->Mode = PID_MODE_NONE; |
|
1787 } /* fridge beer or profile mode */ |
|
1788 |
|
1789 /* |
|
1790 * Now everything is set and done, update the LCD display |
|
1791 */ |
|
1792 LCDair = unit->air_temperature / 1000.0; |
|
1793 LCDbeer = unit->beer_temperature / 1000.0; |
|
1794 LCDstatC = LCDstatH = ' '; |
|
1795 if (unit->heater_address) { |
|
1796 if (unit->heater_state) |
|
1797 LCDstatH = '\6'; |
|
1798 else |
|
1799 LCDstatH = '\5'; |
|
1800 } |
|
1801 if (unit->cooler_address) { |
|
1802 if (unit->cooler_state) |
|
1803 LCDstatC = '\4'; |
|
1804 else |
|
1805 LCDstatC = '\3'; |
|
1806 } |
|
1807 LCDspH = LCDspL = 0.0; |
|
1808 if (unit->mode == UNITMODE_BEER) { |
|
1809 LCDspH = unit->beer_set_hi; |
|
1810 LCDspL = unit->beer_set_lo; |
|
1811 } else if (unit->mode == UNITMODE_FRIDGE) { |
|
1812 LCDspH = unit->fridge_set_hi; |
|
1813 LCDspL = unit->fridge_set_lo; |
|
1814 } else if (unit->mode == UNITMODE_PROFILE) { |
|
1815 if (unit->prof_state != PROFILE_OFF) { |
|
1816 LCDspL = unit->prof_target_lo; |
|
1817 LCDspH = unit->prof_target_hi; |
|
1818 } |
|
1819 } |
|
1820 // if ((seconds == 60) && ((unit->mode == UNITMODE_FRIDGE) || (unit->mode == UNITMODE_BEER) || (unit->mode == UNITMODE_PROFILE))) { |
|
1821 // unit->mqtt_flag |= MQTT_FLAG_DATA; |
|
1822 // } |
|
1823 if (seconds == 60) { |
|
1824 unit->mqtt_flag |= MQTT_FLAG_DATA; |
|
1825 } |
|
1826 pthread_mutex_lock(&mutexes[LOCK_LCD]); |
|
1827 /* |
|
1828 * Write 4 rows to the LCD to display the unit state |
|
1829 */ |
|
1830 lcd_buf_write(row++, "Unit %d: %s ", LCDunit, UNITMODE[unit->mode]); |
|
1831 lcd_buf_write(row++, "%s ", unit->product_name); |
|
1832 lcd_buf_write(row++, "%c%5.1f\2 A%6.2f\1 ", LCDstatC, LCDspH, LCDair); |
|
1833 lcd_buf_write(row++, "%c%5.1f\2 B%6.2f\1 ", LCDstatH, LCDspL, LCDbeer); |
|
1834 pthread_mutex_unlock(&mutexes[LOCK_LCD]); |
|
1835 |
|
1836 /* |
|
1837 * Publish MQTT messages set in flag |
|
1838 */ |
|
1839 if (unit->mqtt_flag) { |
|
1840 if (unit->mqtt_flag & MQTT_FLAG_BIRTH) { |
|
1841 publishDBirth(unit); |
|
1842 unit->mqtt_flag &= ~MQTT_FLAG_BIRTH; |
|
1843 } else { |
|
1844 publishDData(unit); |
|
1845 unit->mqtt_flag &= ~MQTT_FLAG_DATA; |
|
1846 } |
|
1847 if (unit->mqtt_flag & MQTT_FLAG_DEATH) { |
|
1848 publishDDeath(unit); |
|
1849 unit->mqtt_flag &= ~MQTT_FLAG_DEATH; |
|
1850 } |
|
1851 } |
|
1852 |
|
1853 /* |
|
1854 * Handle changed alarms |
|
1855 */ |
|
1856 if (unit->alarm_flag != unit->alarm_last) { |
|
1857 syslog(LOG_NOTICE, "Unit `%s' Alarm %d => %d", unit->alias, unit->alarm_last, unit->alarm_flag); |
|
1858 unit->alarm_last = unit->alarm_flag; |
|
1859 } |
|
1860 } /* for units */ |
1870 } /* for units */ |
1861 |
1871 |
1862 pthread_mutex_lock(&mutexes[LOCK_MENU]); |
1872 pthread_mutex_lock(&mutexes[LOCK_MENU]); |
1863 if (setupmenu == MENU_NONE) { |
1873 if (setupmenu == MENU_NONE) { |
1864 pthread_mutex_lock(&mutexes[LOCK_LCD]); |
1874 pthread_mutex_lock(&mutexes[LOCK_LCD]); |