Kiln waits to reach target temperature before transitioning to the next temperature.

pull/1/head
Nick Sifniotis 2019-07-14 14:39:56 +10:00
rodzic e8957f7dec
commit af483e34f8
3 zmienionych plików z 122 dodań i 56 usunięć

Wyświetl plik

@ -59,6 +59,11 @@ class Oven (threading.Thread):
def __init__(self, simulate=False, time_step=config.sensor_time_wait):
threading.Thread.__init__(self)
self.profile = None
self.start_time = 0
self.runtime = 0
self.target = 0
self.state = Oven.STATE_IDLE
self.daemon = True
self.simulate = simulate
self.time_step = time_step
@ -78,7 +83,6 @@ class Oven (threading.Thread):
self.profile = None
self.start_time = 0
self.runtime = 0
self.totaltime = 0
self.target = 0
self.door = self.get_door_state()
self.state = Oven.STATE_IDLE
@ -90,7 +94,7 @@ class Oven (threading.Thread):
def run_profile(self, profile):
log.info("Running profile %s" % profile.name)
self.profile = profile
self.totaltime = profile.get_duration()
self.profile.running = True
self.state = Oven.STATE_RUNNING
self.start_time = datetime.datetime.now()
log.info("Starting")
@ -111,8 +115,8 @@ class Oven (threading.Thread):
else:
runtime_delta = datetime.datetime.now() - self.start_time
self.runtime = runtime_delta.total_seconds()
log.info("running at %.1f deg C (Target: %.1f) , heat %.2f, cool %.2f, air %.2f, door %s (%.1fs/%.0f)" % (self.temp_sensor.temperature, self.target, self.heat, self.cool, self.air, self.door, self.runtime, self.totaltime))
self.target = self.profile.get_target_temperature(self.runtime)
log.info("running at %.1f deg C (Target: %.1f) , heat %.2f, cool %.2f, air %.2f, door %s (%.1fs/%.0f)" % (self.temp_sensor.temperature, self.target, self.heat, self.cool, self.air, self.door, self.runtime, self.profile.get_duration()))
self.target = self.profile.get_target_temperature(self.runtime, self.temp_sensor.temperature)
pid = self.pid.compute(self.target, self.temp_sensor.temperature)
log.info("pid: %.3f" % pid)
@ -134,27 +138,18 @@ class Oven (threading.Thread):
else:
temperature_count = 0
#Capture the last temperature value. This must be done before set_heat, since there is a sleep in there now.
# Capture the last temperature value. This must be done before set_heat, since there is a sleep
last_temp = self.temp_sensor.temperature
self.set_heat(pid)
#if self.profile.is_rising(self.runtime):
# self.set_cool(False)
# self.set_heat(self.temp_sensor.temperature < self.target)
#else:
# self.set_heat(False)
# self.set_cool(self.temp_sensor.temperature > self.target)
if self.temp_sensor.temperature > 200:
self.set_air(False)
elif self.temp_sensor.temperature < 180:
self.set_air(True)
if self.runtime >= self.totaltime:
if self.profile.finished():
self.reset()
if pid > 0:
time.sleep(self.time_step * (1 - pid))
else:
@ -209,7 +204,7 @@ class Oven (threading.Thread):
'heat': self.heat,
'cool': self.cool,
'air': self.air,
'totaltime': self.totaltime,
'totaltime': self.profile.get_duration() if self.profile else 0,
'door': self.door
}
return state
@ -310,46 +305,99 @@ class TempSensorSimulate(TempSensor):
time.sleep(self.sleep_time)
class Profile():
class Profile:
def __init__(self, json_data):
obj = json.loads(json_data)
self.name = obj["name"]
self.data = sorted(obj["data"])
self.data = [ (0, 0) ] + sorted(obj["data"])
self.timeDiffs = [ (0, 0) ]
for i in range(1, len(self.data)):
self.timeDiffs.append( ( self.data[i][0] - self.data[i-1][0], self.data[i][1] ) )
self.currentState = 1
self.numStates = len(self.timeDiffs)
self.running = False
self.lastStateChange = 0
self.totalTime = self.data[-1][0]
self.overtime = 0
log.info(str(self.timeDiffs))
log.info(str(self.totalTime))
def finished(self):
return not self.running
def get_duration(self):
return max([t for (t, x) in self.data])
def get_surrounding_points(self, time):
if time > self.get_duration():
return (None, None)
return self.totalTime + self.overtime
def get_surrounding_points(self):
prev_point = None
next_point = None
for i in range(len(self.data)):
if time < self.data[i][0]:
prev_point = self.data[i-1]
next_point = self.data[i]
break
if self.currentState < self.numStates:
prev_point = self.timeDiffs[self.currentState - 1]
next_point = self.timeDiffs[self.currentState]
return (prev_point, next_point)
return prev_point, next_point
def is_rising(self, time):
(prev_point, next_point) = self.get_surrounding_points(time)
def is_rising(self):
(prev_point, next_point) = self.get_surrounding_points()
if prev_point and next_point:
return prev_point[1] < next_point[1]
else:
return False
def get_target_temperature(self, time):
if time > self.get_duration():
return 0
def get_target_temperature(self, time, temperature):
relativeTime = time - self.lastStateChange
minimumTime = self.timeDiffs[self.currentState][0]
(prev_point, next_point) = self.get_surrounding_points(time)
if relativeTime < minimumTime:
targetTemp = self.get_intermediate_temperature(relativeTime)
else:
if self.check_target(temperature):
# phase transition
self.currentState += 1
self.lastStateChange = time
incl = float(next_point[1] - prev_point[1]) / float(next_point[0] - prev_point[0])
temp = prev_point[1] + (time - prev_point[0]) * incl
return temp
self.totalTime += self.overtime
self.overtime = 0
targetTemp = self.get_intermediate_temperature(0)
if self.currentState == self.numStates:
self.running = False
else:
targetTemp = self.timeDiffs[self.currentState][1]
self.overtime = relativeTime - minimumTime
return targetTemp
def get_intermediate_temperature(self, relativeTime):
(prev_point, next_point) = self.get_surrounding_points()
if next_point[0] == 0:
targetTemp = next_point[1]
else:
incl = float(next_point[1] - prev_point[1]) / float(next_point[0])
targetTemp = prev_point[1] + (relativeTime * incl)
return targetTemp
"""
Tests to see if the target temperature has been acquired.
"""
def check_target(self, temperature):
previous, next = self.get_surrounding_points()
result = True
if previous[1] < next[1]:
if temperature < next[1]:
result = False
elif previous[1] > next[1]:
if temperature > next[1]:
result = False
return result
class PID():

Wyświetl plik

@ -1,6 +1,6 @@
var state = "IDLE";
var state_last = "";
var graph = [ 'profile', 'live'];
var graph = [ 'profile', 'live', 'movingProfile'];
var points = [];
var profiles = [];
var time_mode = 0;
@ -40,7 +40,14 @@ graph.live =
color: "#d8d3c5",
draggable: false
};
graph.movingProfile =
{
label: "Live Profile",
data: [],
points: { show: false },
color: "#ffd300",
draggable: false
};
function updateProfile(id)
{
@ -54,7 +61,7 @@ function updateProfile(id)
$('#sel_prof_eta').html(job_time);
$('#sel_prof_cost').html(kwh + ' kWh ('+ currency_type +': '+ cost +')');
graph.profile.data = profiles[id].data;
graph.plot = $.plot("#graph_container", [ graph.profile, graph.live ] , getOptions());
graph.plot = $.plot("#graph_container", [ graph.profile, graph.live, graph.movingProfile ] , getOptions());
}
function deleteProfile()
@ -79,7 +86,7 @@ function deleteProfile()
$('#e2').select2('val', 0);
graph.profile.points.show = false;
graph.profile.draggable = false;
graph.plot = $.plot("#graph_container", [ graph.profile, graph.live ], getOptions());
graph.plot = $.plot("#graph_container", [ graph.profile, graph.live, graph.movingProfile ], getOptions());
}
@ -143,7 +150,7 @@ function updateProfileTable()
graph.profile.data[row][col] = value;
}
graph.plot = $.plot("#graph_container", [ graph.profile, graph.live ], getOptions());
graph.plot = $.plot("#graph_container", [ graph.profile, graph.live, graph.movingProfile ], getOptions());
}
updateProfileTable();
@ -211,7 +218,8 @@ function runTask()
}
graph.live.data = [];
graph.plot = $.plot("#graph_container", [ graph.profile, graph.live ] , getOptions());
graph.movingProfile.data = [];
graph.plot = $.plot("#graph_container", [ graph.profile, graph.live, graph.movingProfile ] , getOptions());
ws_control.send(JSON.stringify(cmd));
@ -226,7 +234,8 @@ function runTaskSimulation()
}
graph.live.data = [];
graph.plot = $.plot("#graph_container", [ graph.profile, graph.live ] , getOptions());
graph.movingProfile.data = [];
graph.plot = $.plot("#graph_container", [ graph.profile, graph.live, graph.movingProfile ] , getOptions());
ws_control.send(JSON.stringify(cmd));
@ -251,7 +260,7 @@ function enterNewMode()
graph.profile.points.show = true;
graph.profile.draggable = true;
graph.profile.data = [];
graph.plot = $.plot("#graph_container", [ graph.profile, graph.live ], getOptions());
graph.plot = $.plot("#graph_container", [ graph.profile, graph.live, graph.movingProfile ], getOptions());
updateProfileTable();
}
@ -266,7 +275,7 @@ function enterEditMode()
$('#form_profile_name').val(profiles[selected_profile].name);
graph.profile.points.show = true;
graph.profile.draggable = true;
graph.plot = $.plot("#graph_container", [ graph.profile, graph.live ], getOptions());
graph.plot = $.plot("#graph_container", [ graph.profile, graph.live, graph.movingProfile ], getOptions());
updateProfileTable();
}
@ -282,7 +291,7 @@ function leaveEditMode()
$('#profile_table').slideUp();
graph.profile.points.show = false;
graph.profile.draggable = false;
graph.plot = $.plot("#graph_container", [ graph.profile, graph.live ], getOptions());
graph.plot = $.plot("#graph_container", [ graph.profile, graph.live, graph.movingProfile ], getOptions());
}
function newPoint()
@ -296,14 +305,14 @@ function newPoint()
var pointx = 0;
}
graph.profile.data.push([pointx, Math.floor((Math.random()*230)+25)]);
graph.plot = $.plot("#graph_container", [ graph.profile, graph.live ], getOptions());
graph.plot = $.plot("#graph_container", [ graph.profile, graph.live, graph.movingProfile ], getOptions());
updateProfileTable();
}
function delPoint()
{
graph.profile.data.splice(-1,1)
graph.plot = $.plot("#graph_container", [ graph.profile, graph.live ], getOptions());
graph.plot = $.plot("#graph_container", [ graph.profile, graph.live, graph.movingProfile ], getOptions());
updateProfileTable();
}
@ -500,6 +509,7 @@ $(document).ready(function()
$.each(x.log, function(i,v) {
graph.live.data.push([v.runtime, v.temperature]);
graph.movingProfile.data.push([v.runtime, v.target]);
graph.plot = $.plot("#graph_container", [ graph.profile, graph.live ] , getOptions());
});
}
@ -533,22 +543,25 @@ $(document).ready(function()
$("#nav_stop").show();
graph.live.data.push([x.runtime, x.temperature]);
graph.plot = $.plot("#graph_container", [ graph.profile, graph.live ] , getOptions());
graph.movingProfile.data.push([x.runtime, x.target]);
graph.plot = $.plot("#graph_container", [ graph.profile, graph.live, graph.movingProfile ] , getOptions());
timeElapsed = new Date(parseInt(x.runtime) * 1000).toISOString().substr(11, 8);
left = parseInt(x.totaltime-x.runtime);
if (left < 0) { left = 0; }
eta = new Date(left * 1000).toISOString().substr(11, 8);
updateProgress(parseFloat(x.runtime)/parseFloat(x.totaltime)*100);
$('#state').html('<span class="" style="font-size: 22px; font-weight: normal"></span><span style="font-family: Digi; font-size: 40px;">' + eta + '</span>');
$('#timeElapsed').html('<span class="" style="font-size: 22px; font-weight: normal"></span><span style="font-family: Digi; font-size: 40px;">' + timeElapsed + '</span>');
$('#target_temp').html(parseInt(x.target));
}
else
{
$("#nav_start").show();
$("#nav_stop").hide();
$('#state').html('<p class="ds-text">'+state+'</p>');
$('#timeElapsed').html('<p class="ds-text">'+state+'</p>');
}
$('#act_temp').html(parseInt(x.temperature));
@ -616,7 +629,7 @@ $(document).ready(function()
console.log (e.data);
x = JSON.parse(e.data);
graph.live.data.push([x.runtime, x.temperature]);
graph.plot = $.plot("#graph_container", [ graph.profile, graph.live ] , getOptions());
graph.plot = $.plot("#graph_container", [ graph.profile, graph.live, graph.movingProfile ] , getOptions());
}

Wyświetl plik

@ -34,12 +34,17 @@
</div>
</div>
<!-- <div class="clearfix visible-xs-inline"></div> -->
<div class="col-xs-8 col-md-5">
<div class="col-xs-8 col-md-3">
<div class="ds-title">Time Remaining</div><br>
<div class="display ds-num ds-text" id="state">
</div>
</div>
<div class="col-xs-4 col-md-3">
<div class="col-xs-8 col-md-3">
<div class="ds-title">Time Elapsed</div><br>
<div class="display ds-num ds-text" id="timeElapsed">
</div>
</div>
<div class="col-xs-4 col-md-2">
<div class="ds-state ds-title">&nbsp;</div><br>
<div class="display pull-right ds-state" style="padding-right:0">
<span class="ds-led" id="heat">&#92;</span>