diff --git a/config.py.EXAMPLE b/config.py.EXAMPLE index 242e505..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) @@ -26,6 +30,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 @@ -60,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 a294a99..74059cb 100644 --- a/lib/oven.py +++ b/lib/oven.py @@ -146,11 +146,17 @@ class Oven (threading.Thread): 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: @@ -209,13 +215,14 @@ class TempSensorReal(TempSensor): 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 6d9e23a..29169ac 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(): @@ -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') @@ -129,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() @@ -168,6 +188,22 @@ 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 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 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 82c3972..206ba7e 100644 --- a/public/assets/js/picoreflow.js +++ b/public/assets/js/picoreflow.js @@ -3,15 +3,24 @@ 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 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,23 +45,39 @@ 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()); } 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()); } @@ -78,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 += ''; } @@ -107,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) @@ -193,6 +258,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; @@ -291,10 +357,6 @@ function saveProfile() leaveEditMode(); } - - - - function getOptions() { @@ -336,9 +398,7 @@ function getOptions() yaxis: { - tickSize: 25, min: 0, - max: 250, tickDecimals: 0, draggable: false, tickColor: 'rgba(216, 211, 197, 0.2)', @@ -444,11 +504,6 @@ $(document).ready(function() { state = x.state; - if (x.door == "OPEN") - { - - } - if (state!=state_last) { if(state_last == "RUNNING") @@ -477,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 { @@ -494,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() @@ -558,6 +650,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
-
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) + '