From 6b0492bc8c5d7c2ace6d6991a12de85acf531243 Mon Sep 17 00:00:00 2001 From: bogdanstate Date: Sat, 12 Mar 2016 12:16:29 -0800 Subject: [PATCH 1/4] Added delete profile functionality. --- picoreflowd.py | 14 +++++++++++++ public/assets/js/picoreflow.js | 38 +++++++++++++++++++++++++++++----- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/picoreflowd.py b/picoreflowd.py index 6d9e23a..2617abc 100755 --- a/picoreflowd.py +++ b/picoreflowd.py @@ -110,6 +110,13 @@ def handle_storage(): if message == "GET": log.info("GET command recived") wsock.send(get_profiles()) + elif msgdict.get("cmd") == "DELETE": + log.info("DELETE command received") + profile_obj = msgdict.get('profile') + if delete_profile(profile_obj): + msgdict["resp"] = "OK" + wsock.send(json.dumps(msgdict)) + #wsock.send(get_profiles()) elif msgdict.get("cmd") == "PUT": log.info("PUT command received") profile_obj = msgdict.get('profile') @@ -168,6 +175,13 @@ def save_profile(profile, force=False): log.info("Wrote %s" % filepath) return True +def delete_profile(profile): + profile_json = json.dumps(profile) + filename = profile['name']+".json" + filepath = os.path.join(profile_path, filename) + os.remove(filepath) + log.info("Deleted %s" % filepath) + return True def main(): ip = config.listening_ip diff --git a/public/assets/js/picoreflow.js b/public/assets/js/picoreflow.js index 82c3972..746e34d 100644 --- a/public/assets/js/picoreflow.js +++ b/public/assets/js/picoreflow.js @@ -3,9 +3,9 @@ var state_last = ""; var graph = [ 'profile', 'live']; var points = []; var profiles = []; -var selected_profile = 0; var time_mode = 0; -var selected_profile_name = "leadfree"; +var selected_profile = 0; +var selected_profile_name = 'leadfree'; var host = "ws://" + window.location.hostname + ":" + window.location.port; var ws_status = new WebSocket(host+"/status"); @@ -50,9 +50,26 @@ function updateProfile(id) function deleteProfile() { + var profile = { "type": "profile", "data": "", "name": selected_profile_name }; + var delete_struct = { "cmd": "DELETE", "profile": profile }; + + var delete_cmd = JSON.stringify(delete_struct); console.log("Delete profile:" + selected_profile_name); - // FIXME: Add cmd for socket communication to delete stored profile - leaveEditMode(); + + ws_storage.send(delete_cmd); + + selected_profile_name = profiles[0].name; + ws_storage.send('GET'); + state="IDLE"; + $('#edit').hide(); + $('#profile_selector').show(); + $('#btn_controls').show(); + $('#status').slideDown(); + $('#profile_table').slideUp(); + $('#e2').select2('val', 0); + graph.profile.points.show = false; + graph.profile.draggable = false; + graph.plot = $.plot("#graph_container", [ graph.profile, graph.live ], getOptions()); } @@ -193,6 +210,7 @@ function enterEditMode() $('#edit').show(); $('#profile_selector').hide(); $('#btn_controls').hide(); + console.log(profiles); $('#form_profile_name').attr('value', profiles[selected_profile].name); graph.profile.points.show = true; graph.profile.draggable = true; @@ -558,6 +576,16 @@ $(document).ready(function() profiles = message; //delete old options in select $('#e2').find('option').remove().end(); + // check if current selected value is a valid profile name + // if not, update with first available profile name + var valid_profile_names = profiles.map(function(a) {return a.name;}); + if ( + valid_profile_names.length > 0 && + $.inArray(selected_profile_name, valid_profile_names) === -1 + ) { + selected_profile = 0; + selected_profile_name = valid_profile_names[0]; + } // fill select with new options from websocket for (var i=0; i Date: Thu, 7 Apr 2016 14:59:08 +0100 Subject: [PATCH 2/4] Changed from hard coded relative path to detect the scripts path and dynamically generate the correct path. This means that it can be run from any directory which is useful with startup scripts. --- picoreflowd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/picoreflowd.py b/picoreflowd.py index 2617abc..00506b5 100755 --- a/picoreflowd.py +++ b/picoreflowd.py @@ -44,7 +44,7 @@ def index(): @app.route('/picoreflow/:filename#.*#') def send_static(filename): log.debug("serving %s" % filename) - return bottle.static_file(filename, root='./public/') + return bottle.static_file(filename, root=os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])), "public")) def get_websocket_from_request(): From 0e14a20cbfffbf36e4eac611647dc4d58ffe6b7f Mon Sep 17 00:00:00 2001 From: Rob Shaw Date: Thu, 7 Apr 2016 15:51:46 +0100 Subject: [PATCH 3/4] Added config option to invert the polarity of the heater --- config.py.EXAMPLE | 2 ++ lib/oven.py | 10 ++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/config.py.EXAMPLE b/config.py.EXAMPLE index 242e505..bf15d37 100644 --- a/config.py.EXAMPLE +++ b/config.py.EXAMPLE @@ -26,6 +26,8 @@ gpio_heat = 11 # Switches zero-cross solid-state-relay gpio_cool = 10 # Regulates PWM for 12V DC Blower gpio_air = 9 # Switches 0-phase det. solid-state-relay +heater_invert = 0 # switches the polarity of the heater control + ### Inputs gpio_door = 18 diff --git a/lib/oven.py b/lib/oven.py index a294a99..3c37d73 100644 --- a/lib/oven.py +++ b/lib/oven.py @@ -146,11 +146,17 @@ def set_heat(self, value): if value: self.heat = 1.0 if gpio_available: - GPIO.output(config.gpio_heat, GPIO.LOW) + if config.heater_invert: + GPIO.output(config.gpio_heat, GPIO.LOW) + else: + GPIO.output(config.gpio_heat, GPIO.HIGH) else: self.heat = 0.0 if gpio_available: - GPIO.output(config.gpio_heat, GPIO.HIGH) + if config.heater_invert: + GPIO.output(config.gpio_heat, GPIO.HIGH) + else: + GPIO.output(config.gpio_heat, GPIO.LOW) def set_cool(self, value): if value: From 43ed42245ede85a8cd1da1ac5dada3b54178abd5 Mon Sep 17 00:00:00 2001 From: Andy Rawson Date: Thu, 7 Jul 2016 23:41:06 -0500 Subject: [PATCH 4/4] Add time and temperature options --- config.py.EXAMPLE | 14 ++++ lib/oven.py | 5 +- picoreflowd.py | 22 ++++++ public/assets/css/picoreflow.css | 12 ++- public/assets/js/picoreflow.js | 132 ++++++++++++++++++++++++------- public/index.html | 6 +- 6 files changed, 156 insertions(+), 35 deletions(-) diff --git a/config.py.EXAMPLE b/config.py.EXAMPLE index bf15d37..2dbf4c0 100644 --- a/config.py.EXAMPLE +++ b/config.py.EXAMPLE @@ -12,6 +12,10 @@ log_format = '%(asctime)s %(levelname)s %(name)s: %(message)s' listening_ip = "0.0.0.0" listening_port = 8081 +### Cost Estimate +kwh_rate = 0.26 # Rate in currency_type to calculate cost to run job +currency_type = "EUR" # Currency Symbol to show when calculating cost to run job + ######################################################################## # # GPIO Setup (BCM SoC Numbering Schema) @@ -62,3 +66,13 @@ sim_R_o_nocool = 1.0 # K/W thermal resistance oven -> environment sim_R_o_cool = 0.05 # K/W " with cooling sim_R_ho_noair = 0.1 # K/W thermal resistance heat element -> oven sim_R_ho_air = 0.05 # K/W " with internal air circulation + + +######################################################################## +# +# Time and Temperature parameters + +temp_scale = "c" # c = Celsius | f = Fahrenheit - Unit to display +time_scale_slope = "s" # s = Seconds | m = Minutes | h = Hours - Slope displayed in temp_scale per time_scale_slope +time_scale_profile = "s" # s = Seconds | m = Minutes | h = Hours - Enter and view target time in time_scale_profile + diff --git a/lib/oven.py b/lib/oven.py index 3c37d73..74059cb 100644 --- a/lib/oven.py +++ b/lib/oven.py @@ -215,13 +215,14 @@ def __init__(self, time_step): self.thermocouple = MAX6675(config.gpio_sensor_cs, config.gpio_sensor_clock, config.gpio_sensor_data, - "c") + config.temp_scale) + if config.max31855: log.info("init MAX31855") self.thermocouple = MAX31855(config.gpio_sensor_cs, config.gpio_sensor_clock, config.gpio_sensor_data, - "c") + config.temp_scale) def run(self): while True: diff --git a/picoreflowd.py b/picoreflowd.py index 00506b5..29169ac 100755 --- a/picoreflowd.py +++ b/picoreflowd.py @@ -136,6 +136,19 @@ def handle_storage(): log.info("websocket (storage) closed") +@app.route('/config') +def handle_config(): + wsock = get_websocket_from_request() + log.info("websocket (config) opened") + while True: + try: + message = wsock.receive() + wsock.send(get_config()) + except WebSocketError: + break + log.info("websocket (config) closed") + + @app.route('/status') def handle_status(): wsock = get_websocket_from_request() @@ -183,6 +196,15 @@ def delete_profile(profile): log.info("Deleted %s" % filepath) return True + +def get_config(): + return json.dumps({"temp_scale": config.temp_scale, + "time_scale_slope": config.time_scale_slope, + "time_scale_profile": config.time_scale_profile, + "kwh_rate": config.kwh_rate, + "currency_type": config.currency_type}) + + def main(): ip = config.listening_ip port = config.listening_port diff --git a/public/assets/css/picoreflow.css b/public/assets/css/picoreflow.css index d37e898..8e77db7 100644 --- a/public/assets/css/picoreflow.css +++ b/public/assets/css/picoreflow.css @@ -135,6 +135,16 @@ body { background: radial-gradient(ellipse at center, rgba(242,195,67,1) 0%,rgba(241,218,54,0.26) 100%); /* W3C */ } +.ds-led-door-open { + color: rgb(214, 42, 0); + background: -moz-radial-gradient(center, ellipse cover, rgba(242,195,67,1) 0%, rgba(241,218,54,0.26) 100%); /* FF3.6+ */ + background: -webkit-gradient(radial, center center, 0px, center center, 100%, color-stop(0%,rgba(242,195,67,1)), color-stop(100%,rgba(241,218,54,0.26))); /* Chrome,Safari4+ */ + background: -webkit-radial-gradient(center, ellipse cover, rgba(242,195,67,1) 0%,rgba(241,218,54,0.26) 100%); /* Chrome10+,Safari5.1+ */ + background: -o-radial-gradient(center, ellipse cover, rgba(242,195,67,1) 0%,rgba(241,218,54,0.26) 100%); /* Opera 12+ */ + background: -ms-radial-gradient(center, ellipse cover, rgba(242,195,67,1) 0%,rgba(241,218,54,0.26) 100%); /* IE10+ */ + background: radial-gradient(ellipse at center, rgba(242,195,67,1) 0%,rgba(241,218,54,0.26) 100%); /* W3C */ +} + .ds-led-cool-active { color: rgb(74, 159, 255); background: -moz-radial-gradient(center, ellipse cover, rgba(124,197,239,1) 0%, rgba(48,144,209,0.26) 100%); /* FF3.6+ */ @@ -188,7 +198,7 @@ body { border: none; width: initial; text-align: center; - width: 168px; + width: 210px; padding: 0; } diff --git a/public/assets/js/picoreflow.js b/public/assets/js/picoreflow.js index 746e34d..206ba7e 100644 --- a/public/assets/js/picoreflow.js +++ b/public/assets/js/picoreflow.js @@ -6,12 +6,21 @@ var profiles = []; var time_mode = 0; var selected_profile = 0; var selected_profile_name = 'leadfree'; +var temp_scale = "c"; +var time_scale_slope = "s"; +var time_scale_profile = "s"; +var time_scale_long = "Seconds"; +var temp_scale_display = "C"; +var kwh_rate = 0.26; +var currency_type = "EUR"; var host = "ws://" + window.location.hostname + ":" + window.location.port; var ws_status = new WebSocket(host+"/status"); var ws_control = new WebSocket(host+"/control"); +var ws_config = new WebSocket(host+"/config"); var ws_storage = new WebSocket(host+"/storage"); + if(window.webkitRequestAnimationFrame) window.requestAnimationFrame = window.webkitRequestAnimationFrame; graph.profile = @@ -36,14 +45,13 @@ graph.live = function updateProfile(id) { selected_profile = id; - job_time = parseInt(profiles[id].data[profiles[id].data.length-1][0]); - var kwh = (3850*job_time/3600/1000).toFixed(2); - var cost = (kwh*0.26).toFixed(2); - var minutes = Math.floor(job_time/60), seconds = job_time-minutes*60; - job_time = minutes+':'+ (seconds < 10 ? "0" : "") + seconds; + var job_seconds = parseInt(profiles[id].data[profiles[id].data.length-1][0]); + var kwh = (3850*job_seconds/3600/1000).toFixed(2); + var cost = (kwh*kwh_rate).toFixed(2); + var job_time = new Date(job_seconds * 1000).toISOString().substr(11, 8); $('#sel_prof').html(profiles[id].name); $('#sel_prof_eta').html(job_time); - $('#sel_prof_cost').html(kwh + ' kWh (EUR: '+ cost +')'); + $('#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()); } @@ -95,20 +103,20 @@ function updateProfileTable() var color = ""; var html = '

Profile Points

'; - html += ''; + html += ''; for(var i=0; i=1) dps = Math.round( (graph.profile.data[i][1]-graph.profile.data[i-1][1])/(graph.profile.data[i][0]-graph.profile.data[i-1][0]) * 10) / 10; + + if (i>=1) dps = ((graph.profile.data[i][1]-graph.profile.data[i-1][1])/(graph.profile.data[i][0]-graph.profile.data[i-1][0]) * 10) / 10; if (dps > 0) { slope = "up"; color="rgba(206, 5, 5, 1)"; } else if (dps < 0) { slope = "down"; color="rgba(23, 108, 204, 1)"; dps *= -1; } else if (dps == 0) { slope = "right"; color="grey"; } html += ''; - html += ''; + html += ''; html += ''; - html += ''; + html += ''; html += ''; } @@ -124,13 +132,53 @@ function updateProfileTable() var fields = id.split("-"); var col = parseInt(fields[1]); var row = parseInt(fields[2]); - - graph.profile.data[row][col] = value; + + if (col == 0) { + graph.profile.data[row][col] = timeProfileFormatter(value,false); + } + else { + graph.profile.data[row][col] = value; + } + graph.plot = $.plot("#graph_container", [ graph.profile, graph.live ], getOptions()); updateProfileTable(); + }); } +function timeProfileFormatter(val, down) { + var rval = val + switch(time_scale_profile){ + case "m": + if (down) {rval = val / 60;} else {rval = val * 60;} + break; + case "h": + if (down) {rval = val / 3600;} else {rval = val * 3600;} + break; + } + return Math.round(rval); +} + +function formatDPS(val) { + var tval = val; + if (time_scale_slope == "m") { + tval = val * 60; + } + if (time_scale_slope == "h") { + tval = (val * 60) * 60; + } + return Math.round(tval); +} + +function hazardTemp(){ + if (temp_scale == "f") { + return (45 * 9 / 5) + 32 + } + else { + return 45 + } +} + function timeTickFormatter(val) { if (val < 1800) @@ -309,10 +357,6 @@ function saveProfile() leaveEditMode(); } - - - - function getOptions() { @@ -354,9 +398,7 @@ function getOptions() yaxis: { - tickSize: 25, min: 0, - max: 250, tickDecimals: 0, draggable: false, tickColor: 'rgba(216, 211, 197, 0.2)', @@ -462,11 +504,6 @@ $(document).ready(function() { state = x.state; - if (x.door == "OPEN") - { - - } - if (state!=state_last) { if(state_last == "RUNNING") @@ -495,14 +532,13 @@ $(document).ready(function() graph.plot = $.plot("#graph_container", [ graph.profile, graph.live ] , getOptions()); left = parseInt(x.totaltime-x.runtime); - var minutes = Math.floor(left / 60); - var seconds = left - minutes * 60; - eta = minutes+':'+ (seconds < 10 ? "0" : "") + seconds; + eta = new Date(left * 1000).toISOString().substr(11, 8); updateProgress(parseFloat(x.runtime)/parseFloat(x.totaltime)*100); $('#state').html('' + eta + ''); $('#target_temp').html(parseInt(x.target)); + } else { @@ -512,17 +548,55 @@ $(document).ready(function() } $('#act_temp').html(parseInt(x.temperature)); - + if (x.heat > 0.5) { $('#heat').addClass("ds-led-heat-active"); } else { $('#heat').removeClass("ds-led-heat-active"); } if (x.cool > 0.5) { $('#cool').addClass("ds-led-cool-active"); } else { $('#cool').removeClass("ds-led-cool-active"); } if (x.air > 0.5) { $('#air').addClass("ds-led-air-active"); } else { $('#air').removeClass("ds-led-air-active"); } - if (x.temperature > 45) { $('#hazard').addClass("ds-led-hazard-active"); } else { $('#hazard').removeClass("ds-led-hazard-active"); } + if (x.temperature > hazardTemp()) { $('#hazard').addClass("ds-led-hazard-active"); } else { $('#hazard').removeClass("ds-led-hazard-active"); } + if ((x.door == "OPEN") || (x.door == "UNKNOWN")) { $('#door').addClass("ds-led-door-open"); } else { $('#door').removeClass("ds-led-door-open"); } state_last = state; } }; + // Config Socket ///////////////////////////////// + + ws_config.onopen = function() + { + ws_config.send('GET'); + }; + + ws_config.onmessage = function(e) + { + console.log (e.data); + x = JSON.parse(e.data); + temp_scale = x.temp_scale; + time_scale_slope = x.time_scale_slope; + time_scale_profile = x.time_scale_profile; + kwh_rate = x.kwh_rate; + currency_type = x.currency_type; + + if (temp_scale == "c") {temp_scale_display = "C";} else {temp_scale_display = "F";} + + + $('#act_temp_scale').html('º'+temp_scale_display); + $('#target_temp_scale').html('º'+temp_scale_display); + + switch(time_scale_profile){ + case "s": + time_scale_long = "Seconds"; + break; + case "m": + time_scale_long = "Minutes"; + break; + case "h": + time_scale_long = "Hours"; + break; + } + + } + // Control Socket //////////////////////////////// ws_control.onopen = function() diff --git a/public/index.html b/public/index.html index cbec8c4..ef33bed 100644 --- a/public/index.html +++ b/public/index.html @@ -33,10 +33,10 @@
-
25°C
-
---°C
+
25°C
+
---°C
-
\l[I
+
\l[I
#Target TimeTarget Temperature in °CSlope in °C/s
#Target Time in ' + time_scale_long+ 'Target Temperature in °'+temp_scale_display+'Slope in °'+temp_scale_display+'/'+time_scale_slope+'

' + (i+1) + '