Marks Patches
This commit is contained in:
parent
1e2c73f810
commit
b0988ade10
38
config.py
38
config.py
@ -1,3 +1,7 @@
|
||||
##### CHEMATEX *****
|
||||
##### CHEMATEX *****
|
||||
##### CHEMATEX *****
|
||||
|
||||
import logging
|
||||
from lib.max31856 import MAX31856
|
||||
|
||||
@ -14,8 +18,10 @@ listening_ip = "0.0.0.0"
|
||||
listening_port = 8081
|
||||
|
||||
### Cost Estimate
|
||||
kwh_rate = 0.1319 # Rate in currency_type to calculate cost to run job
|
||||
currency_type = "$" # Currency Symbol to show when calculating cost to run job
|
||||
# Mark's cost June 2021 Vattenfall all taxes and fees SEK 42,64 => 42,64 /kWt
|
||||
kwh_rate = 1.0380 # Rate in currency_type to calculate cost to run job
|
||||
currency_type = "Kr" # Currency Symbol to show when calculating cost to run job
|
||||
oven_kw = 10000 # kW capacity of oven
|
||||
|
||||
########################################################################
|
||||
#
|
||||
@ -32,15 +38,15 @@ gpio_heat = 23 # Switches zero-cross solid-state-relay
|
||||
### Thermocouple Adapter selection:
|
||||
# max31855 - bitbang SPI interface
|
||||
# max31856 - bitbang SPI interface. must specify thermocouple_type.
|
||||
max31855 = 1
|
||||
max31856 = 0
|
||||
max31855 = 0
|
||||
max31856 = 1
|
||||
# see lib/max31856.py for other thermocouple_type, only applies to max31856
|
||||
thermocouple_type = MAX31856.MAX31856_S_TYPE
|
||||
|
||||
### Thermocouple Connection (using bitbang interfaces)
|
||||
gpio_sensor_cs = 27
|
||||
gpio_sensor_clock = 22
|
||||
gpio_sensor_data = 17
|
||||
### Thermocouple Connection
|
||||
gpio_sensor_cs = 5
|
||||
gpio_sensor_clock = 11
|
||||
gpio_sensor_data = 9
|
||||
gpio_sensor_di = 10 # only used with max31856
|
||||
|
||||
########################################################################
|
||||
@ -61,9 +67,9 @@ sensor_time_wait = 2
|
||||
# well with the simulated oven. You must tune them to work well with
|
||||
# your specific kiln. Note that the integral pid_ki is
|
||||
# inverted so that a smaller number means more integral action.
|
||||
pid_kp = 25 # Proportional
|
||||
pid_ki = 200 # Integral
|
||||
pid_kd = 200 # Derivative
|
||||
pid_kp = 100 # Proportional
|
||||
pid_ki = 400 # Integral
|
||||
pid_kd = 800 # Derivative
|
||||
|
||||
|
||||
########################################################################
|
||||
@ -80,7 +86,7 @@ stop_integral_windup = True
|
||||
########################################################################
|
||||
#
|
||||
# Simulation parameters
|
||||
simulate = True
|
||||
simulate = False
|
||||
sim_t_env = 60.0 # deg C
|
||||
sim_c_heat = 100.0 # J/K heat capacity of heat element
|
||||
sim_c_oven = 5000.0 # J/K heat capacity of oven
|
||||
@ -98,7 +104,7 @@ sim_R_ho_air = 0.05 # K/W " with internal air circulation
|
||||
# If you change the temp_scale, all settings in this file are assumed to
|
||||
# be in that scale.
|
||||
|
||||
temp_scale = "f" # c = Celsius | f = Fahrenheit - Unit to display
|
||||
temp_scale = "c" # c = Celsius | f = Fahrenheit - Unit to display
|
||||
time_scale_slope = "h" # s = Seconds | m = Minutes | h = Hours - Slope displayed in temp_scale per time_scale_slope
|
||||
time_scale_profile = "m" # s = Seconds | m = Minutes | h = Hours - Enter and view target time in time_scale_profile
|
||||
|
||||
@ -107,7 +113,7 @@ time_scale_profile = "m" # s = Seconds | m = Minutes | h = Hours - Enter and vi
|
||||
# naturally cool off. If your SSR has failed/shorted/closed circuit, this
|
||||
# means your kiln receives full power until your house burns down.
|
||||
# this should not replace you watching your kiln or use of a kiln-sitter
|
||||
emergency_shutoff_temp = 2264 #cone 7
|
||||
emergency_shutoff_temp = 1270 #cone 7
|
||||
|
||||
# If the kiln cannot heat or cool fast enough and is off by more than
|
||||
# kiln_must_catch_up_max_error the entire schedule is shifted until
|
||||
@ -115,7 +121,7 @@ emergency_shutoff_temp = 2264 #cone 7
|
||||
# wanted temperature, the schedule will run forever. This is often used
|
||||
# for heating as fast as possible in a section of a kiln schedule/profile.
|
||||
kiln_must_catch_up = True
|
||||
kiln_must_catch_up_max_error = 10 #degrees
|
||||
kiln_must_catch_up_max_error = 5 #degrees
|
||||
|
||||
# thermocouple offset
|
||||
# If you put your thermocouple in ice water and it reads 36F, you can
|
||||
@ -136,4 +142,4 @@ honour_theromocouple_short_errors = False
|
||||
temperature_average_samples = 40
|
||||
|
||||
# Thermocouple AC frequency filtering - set to True if in a 50Hz locale, else leave at False for 60Hz locale
|
||||
ac_freq_50hz = False
|
||||
ac_freq_50hz = True
|
||||
|
||||
25
docs/logs 2.md
Normal file
25
docs/logs 2.md
Normal file
@ -0,0 +1,25 @@
|
||||
Logs for a Kiln Run
|
||||
===================
|
||||
|
||||
Logs from the app on the pi go to **/var/log/daemon.log** and look like this...
|
||||
|
||||
Jan 21 06:25:40 raspberrypi python[286]: 2019-01-21 06:25:40,390 INFO oven: temp =1092.4, target=1093.2, pid=1.000, heat_on=2.00, heat_off=0.00, run_time=15993, total_time=48780, time_left=32786
|
||||
|
||||
| log variable | meaning |
|
||||
| ------------ | ------- |
|
||||
|temp | temperature read by thermocouple |
|
||||
|target | target temperature |
|
||||
|pid | pid value for that 2s |
|
||||
|heat_on | number of seconds the elements were on |
|
||||
|heat_off | number of seconds the elements were off |
|
||||
|run_time | seconds since start of schedule|
|
||||
|total_time | total seconds for schedule |
|
||||
|time_left | seconds left till the end of schedule|
|
||||
|
||||
It's trivial to convert the data to csv...
|
||||
|
||||
$ grep "INFO oven" daemon.log|sed 's/temp=//'|sed 's/target=//'|sed 's/heat_on=//'|sed 's/heat_off=//'|sed 's/run_time=//'|sed 's/total_time=//'|sed 's/time_left=//'|sed 's/pid=//'|sed 's/.*: //' >out.csv
|
||||
|
||||
Here is a slow glaze firing imported into google docs. Make sure to check out the report tab.
|
||||
|
||||
https://docs.google.com/spreadsheets/d/1Lcp88P1cNNzYWgKDfnd5UaPVqLuBdAT3lkpfcZMKEBM/edit#gid=2116406322
|
||||
@ -1,5 +1,15 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
#### MARK TILLES START BLINKING GREEN LED WHEN SERVICE IS RUNNING
|
||||
from gpiozero import Button, LEDBoard
|
||||
from signal import pause
|
||||
import warnings, os, sys
|
||||
green_ledGPIO = 6
|
||||
green_led=LEDBoard(green_ledGPIO)
|
||||
green_led.blink(on_time=1, off_time=1)
|
||||
#### END - MARK TILLES START BLINKING GREEN LED WHEN SERVICE IS RUNNING
|
||||
|
||||
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
@ -60,13 +70,25 @@ def handle_api():
|
||||
|
||||
# start at a specific minute in the schedule
|
||||
# for restarting and skipping over early parts of a schedule
|
||||
startat = 0;
|
||||
startat = 0;
|
||||
if 'startat' in bottle.request.json:
|
||||
startat = bottle.request.json['startat']
|
||||
|
||||
# get the wanted profile/kiln schedule
|
||||
profile = find_profile(wanted)
|
||||
if profile is None:
|
||||
|
||||
elif msgdict.get("cmd") == "MARK_SWITCH_TO_CHEMATEX":
|
||||
log.info("Switching to Chematex kiln")
|
||||
oven.abort_run()
|
||||
os.system ("/home/pi/mark_scripts/chematex &")
|
||||
# TODO: add system call to actually switch
|
||||
# Added by Henrik for Mark Tilles
|
||||
elif msgdict.get("cmd") == "MARK_SWITCH_TO_RHODE":
|
||||
log.info("Switching to Rhode kiln")
|
||||
oven.abort_run()
|
||||
os.system ("/home/pi/mark_scripts/rhode &")
|
||||
# TODO: add system call to actually switch
|
||||
return { "success" : False, "error" : "profile %s not found" % wanted }
|
||||
|
||||
# FIXME juggling of json should happen in the Profile class
|
||||
@ -260,7 +282,8 @@ def get_config():
|
||||
"time_scale_slope": config.time_scale_slope,
|
||||
"time_scale_profile": config.time_scale_profile,
|
||||
"kwh_rate": config.kwh_rate,
|
||||
"currency_type": config.currency_type})
|
||||
"oven_kw": config.oven_kw,
|
||||
"currency_type": config.currency_type})
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
BIN
lib/.DS_Store
vendored
Normal file
BIN
lib/.DS_Store
vendored
Normal file
Binary file not shown.
@ -13,7 +13,7 @@ class MAX31855(object):
|
||||
'''Initialize Soft (Bitbang) SPI bus
|
||||
|
||||
Parameters:
|
||||
- cs_pin: Chip Select (CS) / Slave Select (SS) pin (Any GPIO)
|
||||
- cs_pin: Chip Select (CS) / Slave Select (SS) pin (Any GPIO)
|
||||
- clock_pin: Clock (SCLK / SCK) pin (Any GPIO)
|
||||
- data_pin: Data input (SO / MOSI) pin (Any GPIO)
|
||||
- units: (optional) unit of measurement to return. ("c" (default) | "k" | "f")
|
||||
@ -26,7 +26,6 @@ class MAX31855(object):
|
||||
self.units = units
|
||||
self.data = None
|
||||
self.board = board
|
||||
self.noConnection = self.shortToGround = self.shortToVCC = self.unknownError = False
|
||||
|
||||
# Initialize needed GPIO
|
||||
GPIO.setmode(self.board)
|
||||
@ -71,13 +70,20 @@ class MAX31855(object):
|
||||
if data_32 is None:
|
||||
data_32 = self.data
|
||||
anyErrors = (data_32 & 0x10000) != 0 # Fault bit, D16
|
||||
noConnection = (data_32 & 0x00000001) != 0 # OC bit, D0
|
||||
shortToGround = (data_32 & 0x00000002) != 0 # SCG bit, D1
|
||||
shortToVCC = (data_32 & 0x00000004) != 0 # SCV bit, D2
|
||||
if anyErrors:
|
||||
self.noConnection = (data_32 & 0x00000001) != 0 # OC bit, D0
|
||||
self.shortToGround = (data_32 & 0x00000002) != 0 # SCG bit, D1
|
||||
self.shortToVCC = (data_32 & 0x00000004) != 0 # SCV bit, D2
|
||||
self.unknownError = not (self.noConnection | self.shortToGround | self.shortToVCC) # Errk!
|
||||
else:
|
||||
self.noConnection = self.shortToGround = self.shortToVCC = self.unknownError = False
|
||||
if noConnection:
|
||||
raise MAX31855Error("No Connection")
|
||||
elif shortToGround:
|
||||
raise MAX31855Error("Thermocouple short to ground")
|
||||
elif shortToVCC:
|
||||
raise MAX31855Error("Thermocouple short to VCC")
|
||||
else:
|
||||
# Perhaps another SPI device is trying to send data?
|
||||
# Did you remember to initialize all other SPI devices?
|
||||
raise MAX31855Error("Unknown Error")
|
||||
|
||||
def data_to_tc_temperature(self, data_32 = None):
|
||||
'''Takes an integer and returns a thermocouple temperature in celsius.'''
|
||||
@ -132,7 +138,7 @@ class MAX31855(object):
|
||||
GPIO.setup(self.clock_pin, GPIO.IN)
|
||||
|
||||
def data_to_LinearizedTempC(self, data_32 = None):
|
||||
'''Return the NIST-linearized thermocouple temperature value in degrees
|
||||
'''Return the NIST-linearized thermocouple temperature value in degrees
|
||||
celsius. See https://learn.adafruit.com/calibrating-sensors/maxim-31855-linearization for more infoo.
|
||||
This code came from https://github.com/nightmechanic/FuzzypicoReflow/blob/master/lib/max31855.py
|
||||
'''
|
||||
|
||||
@ -89,7 +89,7 @@ class MAX31856(object):
|
||||
MAX31856_S_TYPE = 0x6 # Read S Type Thermocouple
|
||||
MAX31856_T_TYPE = 0x7 # Read T Type Thermocouple
|
||||
|
||||
def __init__(self, tc_type=MAX31856_S_TYPE, units="c", avgsel=0x0, ac_freq_50hz=False, ocdetect=0x1, software_spi=None, hardware_spi=None, gpio=None):
|
||||
def __init__(self, tc_type=MAX31856_S_TYPE, units="c", avgsel=0x0, software_spi=None, hardware_spi=None, gpio=None):
|
||||
"""
|
||||
Initialize MAX31856 device with software SPI on the specified CLK,
|
||||
CS, and DO pins. Alternatively can specify hardware SPI by sending an
|
||||
@ -100,8 +100,6 @@ class MAX31856(object):
|
||||
MAX31856.MAX31856_X_TYPE.
|
||||
avgsel (1-byte Hex): Type of Averaging. Choose from values in CR0 table of datasheet.
|
||||
Default is single sample.
|
||||
ac_freq_50hz: Set to True if your AC frequency is 50Hz, Set to False for 60Hz,
|
||||
ocdetect: Detect open circuit errors (ie broken thermocouple). Choose from values in CR1 table of datasheet
|
||||
software_spi (dict): Contains the pin assignments for software SPI, as defined below:
|
||||
clk (integer): Pin number for software SPI clk
|
||||
cs (integer): Pin number for software SPI cs
|
||||
@ -114,8 +112,6 @@ class MAX31856(object):
|
||||
self.tc_type = tc_type
|
||||
self.avgsel = avgsel
|
||||
self.units = units
|
||||
self.noConnection = self.shortToGround = self.shortToVCC = self.unknownError = False
|
||||
|
||||
# Handle hardware SPI
|
||||
if hardware_spi is not None:
|
||||
self._logger.debug('Using hardware SPI')
|
||||
@ -136,13 +132,11 @@ class MAX31856(object):
|
||||
self._spi.set_mode(1)
|
||||
self._spi.set_bit_order(SPI.MSBFIRST)
|
||||
|
||||
self.cr0 = self.MAX31856_CR0_READ_CONT | ((ocdetect & 3) << 4) | (1 if ac_freq_50hz else 0)
|
||||
self.cr1 = (((self.avgsel & 7) << 4) + (self.tc_type & 0x0f))
|
||||
self.cr1 = ((self.avgsel << 4) + self.tc_type)
|
||||
|
||||
# Setup for reading continuously with T-Type thermocouple
|
||||
self._write_register(self.MAX31856_REG_WRITE_CR0, 0)
|
||||
self._write_register(self.MAX31856_REG_WRITE_CR0, self.MAX31856_CR0_READ_CONT)
|
||||
self._write_register(self.MAX31856_REG_WRITE_CR1, self.cr1)
|
||||
self._write_register(self.MAX31856_REG_WRITE_CR0, self.cr0)
|
||||
|
||||
@staticmethod
|
||||
def _cj_temp_from_bytes(msb, lsb):
|
||||
@ -303,39 +297,8 @@ class MAX31856(object):
|
||||
'''Convert celsius to fahrenheit.'''
|
||||
return celsius * 9.0/5.0 + 32
|
||||
|
||||
def checkErrors(self):
|
||||
data = self.read_fault_register()
|
||||
self.noConnection = (data & 0x00000001) != 0
|
||||
self.unknownError = (data & 0xfe) != 0
|
||||
|
||||
def get(self):
|
||||
self.checkErrors()
|
||||
celcius = self.read_temp_c()
|
||||
return getattr(self, "to_" + self.units)(celcius)
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
# Multi-chip example
|
||||
import time
|
||||
cs_pins = [6]
|
||||
clock_pin = 13
|
||||
data_pin = 5
|
||||
di_pin = 26
|
||||
units = "c"
|
||||
thermocouples = []
|
||||
for cs_pin in cs_pins:
|
||||
thermocouples.append(MAX31856(avgsel=0, ac_freq_50hz=True, tc_type=MAX31856.MAX31856_K_TYPE, software_spi={'clk': clock_pin, 'cs': cs_pin, 'do': data_pin, 'di': di_pin}, units=units))
|
||||
|
||||
running = True
|
||||
while(running):
|
||||
try:
|
||||
for thermocouple in thermocouples:
|
||||
rj = thermocouple.read_internal_temp_c()
|
||||
tc = thermocouple.get()
|
||||
print("tc: {} and rj: {}, NC:{} ??:{}".format(tc, rj, thermocouple.noConnection, thermocouple.unknownError))
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
running = False
|
||||
for thermocouple in thermocouples:
|
||||
thermocouple.cleanup()
|
||||
|
||||
157
lib/oven.py
157
lib/oven.py
@ -8,7 +8,6 @@ import config
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Output(object):
|
||||
def __init__(self):
|
||||
self.active = False
|
||||
@ -27,15 +26,13 @@ class Output(object):
|
||||
log.warning(msg)
|
||||
self.active = False
|
||||
|
||||
def heat(self,sleepfor, tuning=False):
|
||||
def heat(self,sleepfor):
|
||||
self.GPIO.output(config.gpio_heat, self.GPIO.HIGH)
|
||||
if tuning:
|
||||
return
|
||||
time.sleep(sleepfor)
|
||||
self.GPIO.output(config.gpio_heat, self.GPIO.LOW)
|
||||
|
||||
def cool(self,sleepfor):
|
||||
'''no active cooling, so sleep'''
|
||||
self.GPIO.output(config.gpio_heat, self.GPIO.LOW)
|
||||
time.sleep(sleepfor)
|
||||
|
||||
# FIX - Board class needs to be completely removed
|
||||
@ -57,7 +54,7 @@ class Board(object):
|
||||
self.active = True
|
||||
log.info("import %s " % (self.name))
|
||||
except ImportError:
|
||||
msg = "max31855 config set, but import failed"
|
||||
msg = "max31855 config set, but import failed"
|
||||
log.warning(msg)
|
||||
|
||||
if config.max31856:
|
||||
@ -67,7 +64,7 @@ class Board(object):
|
||||
self.active = True
|
||||
log.info("import %s " % (self.name))
|
||||
except ImportError:
|
||||
msg = "max31856 config set, but import failed"
|
||||
msg = "max31856 config set, but import failed"
|
||||
log.warning(msg)
|
||||
|
||||
def create_temp_sensor(self):
|
||||
@ -79,15 +76,13 @@ class Board(object):
|
||||
class BoardSimulated(object):
|
||||
def __init__(self):
|
||||
self.temp_sensor = TempSensorSimulated()
|
||||
|
||||
|
||||
class TempSensor(threading.Thread):
|
||||
def __init__(self):
|
||||
threading.Thread.__init__(self)
|
||||
self.daemon = True
|
||||
self.temperature = 0
|
||||
self.bad_percent = 0
|
||||
self.time_step = config.sensor_time_wait
|
||||
self.noConnection = self.shortToGround = self.shortToVCC = self.unknownError = False
|
||||
|
||||
class TempSensorSimulated(TempSensor):
|
||||
'''not much here, just need to be able to set the temperature'''
|
||||
@ -99,11 +94,6 @@ class TempSensorReal(TempSensor):
|
||||
during the time_step'''
|
||||
def __init__(self):
|
||||
TempSensor.__init__(self)
|
||||
self.sleeptime = self.time_step / float(config.temperature_average_samples)
|
||||
self.bad_count = 0
|
||||
self.ok_count = 0
|
||||
self.bad_stamp = 0
|
||||
|
||||
if config.max31855:
|
||||
log.info("init MAX31855")
|
||||
from max31855 import MAX31855, MAX31855Error
|
||||
@ -118,50 +108,31 @@ class TempSensorReal(TempSensor):
|
||||
software_spi = { 'cs': config.gpio_sensor_cs,
|
||||
'clk': config.gpio_sensor_clock,
|
||||
'do': config.gpio_sensor_data,
|
||||
'di': config.gpio_sensor_di }
|
||||
### MARK TILLES ADDED
|
||||
'di': config.gpio_sensor_di,
|
||||
#'gpio': config.gpio_heat
|
||||
}
|
||||
|
||||
self.thermocouple = MAX31856(tc_type=config.thermocouple_type,
|
||||
software_spi = software_spi,
|
||||
units = config.temp_scale,
|
||||
ac_freq_50hz = config.ac_freq_50hz,
|
||||
units = config.temp_scale
|
||||
)
|
||||
|
||||
def run(self):
|
||||
'''use a moving average of config.temperature_average_samples across the time_step'''
|
||||
temps = []
|
||||
'''take 5 measurements over each time period and return the
|
||||
average'''
|
||||
while True:
|
||||
# reset error counter if time is up
|
||||
if (time.time() - self.bad_stamp) > (self.time_step * 2):
|
||||
if self.bad_count + self.ok_count:
|
||||
self.bad_percent = (self.bad_count / (self.bad_count + self.ok_count)) * 100
|
||||
else:
|
||||
self.bad_percent = 0
|
||||
self.bad_count = 0
|
||||
self.ok_count = 0
|
||||
self.bad_stamp = time.time()
|
||||
|
||||
temp = self.thermocouple.get()
|
||||
self.noConnection = self.thermocouple.noConnection
|
||||
self.shortToGround = self.thermocouple.shortToGround
|
||||
self.shortToVCC = self.thermocouple.shortToVCC
|
||||
self.unknownError = self.thermocouple.unknownError
|
||||
|
||||
is_bad_value = self.noConnection | self.unknownError
|
||||
if config.honour_theromocouple_short_errors:
|
||||
is_bad_value |= self.shortToGround | self.shortToVCC
|
||||
|
||||
if not is_bad_value:
|
||||
temps.append(temp)
|
||||
if len(temps) > config.temperature_average_samples:
|
||||
del temps[0]
|
||||
self.ok_count += 1
|
||||
|
||||
else:
|
||||
log.error("Problem reading temp N/C:%s GND:%s VCC:%s ???:%s" % (self.noConnection,self.shortToGround,self.shortToVCC,self.unknownError))
|
||||
self.bad_count += 1
|
||||
|
||||
if len(temps):
|
||||
self.temperature = sum(temps) / len(temps)
|
||||
time.sleep(self.sleeptime)
|
||||
maxtries = 5
|
||||
sleeptime = self.time_step / float(maxtries)
|
||||
temps = []
|
||||
for x in range(0,maxtries):
|
||||
try:
|
||||
temp = self.thermocouple.get()
|
||||
temps.append(temp)
|
||||
except Exception:
|
||||
log.exception("problem reading temp")
|
||||
time.sleep(sleeptime)
|
||||
self.temperature = sum(temps)/len(temps)
|
||||
|
||||
class Oven(threading.Thread):
|
||||
'''parent oven class. this has all the common code
|
||||
@ -184,22 +155,8 @@ class Oven(threading.Thread):
|
||||
self.pid = PID(ki=config.pid_ki, kd=config.pid_kd, kp=config.pid_kp)
|
||||
|
||||
def run_profile(self, profile, startat=0):
|
||||
self.reset()
|
||||
|
||||
if self.board.temp_sensor.noConnection:
|
||||
log.info("Refusing to start profile - thermocouple not connected")
|
||||
return
|
||||
if self.board.temp_sensor.shortToGround:
|
||||
log.info("Refusing to start profile - thermocouple short to ground")
|
||||
return
|
||||
if self.board.temp_sensor.shortToVCC:
|
||||
log.info("Refusing to start profile - thermocouple short to VCC")
|
||||
return
|
||||
if self.board.temp_sensor.unknownError:
|
||||
log.info("Refusing to start profile - thermocouple unknown error")
|
||||
return
|
||||
|
||||
log.info("Running schedule %s" % profile.name)
|
||||
self.reset()
|
||||
self.profile = profile
|
||||
self.totaltime = profile.get_duration()
|
||||
self.state = "RUNNING"
|
||||
@ -229,9 +186,6 @@ class Oven(threading.Thread):
|
||||
|
||||
def update_runtime(self):
|
||||
runtime_delta = datetime.datetime.now() - self.start_time
|
||||
if runtime_delta.total_seconds() < 0:
|
||||
runtime_delta = datetime.timedelta(0)
|
||||
|
||||
if self.startat > 0:
|
||||
self.runtime = self.startat + runtime_delta.total_seconds()
|
||||
else:
|
||||
@ -241,24 +195,12 @@ class Oven(threading.Thread):
|
||||
self.target = self.profile.get_target_temperature(self.runtime)
|
||||
|
||||
def reset_if_emergency(self):
|
||||
'''reset if the temperature is way TOO HOT, or other critical errors detected'''
|
||||
'''reset if the temperature is way TOO HOT'''
|
||||
if (self.board.temp_sensor.temperature + config.thermocouple_offset >=
|
||||
config.emergency_shutoff_temp):
|
||||
log.info("emergency!!! temperature too high, shutting down")
|
||||
self.reset()
|
||||
|
||||
if self.board.temp_sensor.noConnection:
|
||||
log.info("emergency!!! lost connection to thermocouple, shutting down")
|
||||
self.reset()
|
||||
|
||||
if self.board.temp_sensor.unknownError:
|
||||
log.info("emergency!!! unknown thermocouple error, shutting down")
|
||||
self.reset()
|
||||
|
||||
if self.board.temp_sensor.bad_percent > 30:
|
||||
log.info("emergency!!! too many errors in a short period, shutting down")
|
||||
self.reset()
|
||||
|
||||
def reset_if_schedule_ended(self):
|
||||
if self.runtime > self.totaltime:
|
||||
log.info("schedule ended, shutting down")
|
||||
@ -272,8 +214,6 @@ class Oven(threading.Thread):
|
||||
'state': self.state,
|
||||
'heat': self.heat,
|
||||
'totaltime': self.totaltime,
|
||||
'kwh_rate': config.kwh_rate,
|
||||
'currency_type': config.currency_type,
|
||||
'profile': self.profile.name if self.profile else None,
|
||||
}
|
||||
return state
|
||||
@ -309,10 +249,10 @@ class SimulatedOven(Oven):
|
||||
# set temps to the temp of the surrounding environment
|
||||
self.t = self.t_env # deg C temp of oven
|
||||
self.t_h = self.t_env #deg C temp of heating element
|
||||
|
||||
|
||||
# call parent init
|
||||
Oven.__init__(self)
|
||||
|
||||
|
||||
# start thread
|
||||
self.start()
|
||||
log.info("SimulatedOven started")
|
||||
@ -370,9 +310,9 @@ class SimulatedOven(Oven):
|
||||
self.runtime,
|
||||
self.totaltime,
|
||||
time_left))
|
||||
|
||||
|
||||
# we don't actually spend time heating & cooling during
|
||||
# a simulation, so sleep.
|
||||
# a simulation, so sleep.
|
||||
time.sleep(self.time_step)
|
||||
|
||||
|
||||
@ -389,10 +329,6 @@ class RealOven(Oven):
|
||||
# start thread
|
||||
self.start()
|
||||
|
||||
def reset(self):
|
||||
super().reset()
|
||||
self.output.cool(0)
|
||||
|
||||
def heat_then_cool(self):
|
||||
pid = self.pid.compute(self.target,
|
||||
self.board.temp_sensor.temperature +
|
||||
@ -405,10 +341,8 @@ class RealOven(Oven):
|
||||
if heat_on > 0:
|
||||
self.heat = 1.0
|
||||
|
||||
if heat_on:
|
||||
self.output.heat(heat_on)
|
||||
if heat_off:
|
||||
self.output.cool(heat_off)
|
||||
self.output.heat(heat_on)
|
||||
self.output.cool(heat_off)
|
||||
time_left = self.totaltime - self.runtime
|
||||
log.info("temp=%.2f, target=%.2f, pid=%.3f, heat_on=%.2f, heat_off=%.2f, run_time=%d, total_time=%d, time_left=%d" %
|
||||
(self.board.temp_sensor.temperature + config.thermocouple_offset,
|
||||
@ -466,7 +400,7 @@ class PID():
|
||||
self.lastErr = 0
|
||||
|
||||
# FIX - this was using a really small window where the PID control
|
||||
# takes effect from -1 to 1. I changed this to various numbers and
|
||||
# takes effect from -1 to 1. I changed this to various numbers and
|
||||
# settled on -50 to 50 and then divide by 50 at the end. This results
|
||||
# in a larger PID control window and much more accurate control...
|
||||
# instead of what used to be binary on/off control.
|
||||
@ -479,12 +413,8 @@ class PID():
|
||||
error = float(setpoint - ispoint)
|
||||
|
||||
if self.ki > 0:
|
||||
if config.stop_integral_windup == True:
|
||||
if abs(self.kp * error) < window_size:
|
||||
self.iterm += (error * timeDelta * (1/self.ki))
|
||||
else:
|
||||
self.iterm += (error * timeDelta * (1/self.ki))
|
||||
|
||||
self.iterm += (error * timeDelta * (1/self.ki))
|
||||
|
||||
dErr = (error - self.lastErr) / timeDelta
|
||||
output = self.kp * error + self.iterm + self.kd * dErr
|
||||
out4logs = output
|
||||
@ -496,16 +426,15 @@ class PID():
|
||||
if output < 0:
|
||||
output = 0
|
||||
|
||||
#if output > 1:
|
||||
# output = 1
|
||||
|
||||
output = float(output / window_size)
|
||||
|
||||
if out4logs > 0:
|
||||
# log.info("pid percents pid=%0.2f p=%0.2f i=%0.2f d=%0.2f" % (out4logs,
|
||||
# ((self.kp * error)/out4logs)*100,
|
||||
# (self.iterm/out4logs)*100,
|
||||
# ((self.kd * dErr)/out4logs)*100))
|
||||
log.info("pid actuals pid=%0.2f p=%0.2f i=%0.2f d=%0.2f" % (out4logs,
|
||||
self.kp * error,
|
||||
self.iterm,
|
||||
self.kd * dErr))
|
||||
if out4logs > 0:
|
||||
log.info("pid percents pid=%0.2f p=%0.2f i=%0.2f d=%0.2f" % (out4logs,
|
||||
((self.kp * error)/out4logs)*100,
|
||||
(self.iterm/out4logs)*100,
|
||||
((self.kd * dErr)/out4logs)*100))
|
||||
|
||||
return output
|
||||
|
||||
BIN
public/.DS_Store
vendored
Normal file
BIN
public/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
public/assets/.DS_Store
vendored
Normal file
BIN
public/assets/.DS_Store
vendored
Normal file
Binary file not shown.
740
public/assets/js/picoreflow 2.js
Normal file
740
public/assets/js/picoreflow 2.js
Normal file
@ -0,0 +1,740 @@
|
||||
var state = "IDLE";
|
||||
var state_last = "";
|
||||
var graph = [ 'profile', 'live'];
|
||||
var points = [];
|
||||
var profiles = [];
|
||||
var time_mode = 0;
|
||||
var selected_profile = 0;
|
||||
var selected_profile_name = 'cone-05-long-bisque.json';
|
||||
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 protocol = 'ws:';
|
||||
if (window.location.protocol == 'https:') {
|
||||
protocol = 'wss:';
|
||||
}
|
||||
var host = "" + protocol + "//" + 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 =
|
||||
{
|
||||
label: "Profile",
|
||||
data: [],
|
||||
points: { show: false },
|
||||
color: "#75890c",
|
||||
draggable: false
|
||||
};
|
||||
|
||||
graph.live =
|
||||
{
|
||||
label: "Live",
|
||||
data: [],
|
||||
points: { show: false },
|
||||
color: "#d8d3c5",
|
||||
draggable: false
|
||||
};
|
||||
|
||||
|
||||
function updateProfile(id)
|
||||
{
|
||||
selected_profile = id;
|
||||
selected_profile_name = profiles[id].name;
|
||||
var job_seconds = profiles[id].data.length === 0 ? 0 : parseInt(profiles[id].data[profiles[id].data.length-1][0]);
|
||||
var kwh = (oven_kw*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 ('+ 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);
|
||||
|
||||
ws_storage.send(delete_cmd);
|
||||
|
||||
ws_storage.send('GET');
|
||||
selected_profile_name = profiles[0].name;
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
|
||||
function updateProgress(percentage)
|
||||
{
|
||||
if(state=="RUNNING")
|
||||
{
|
||||
if(percentage > 100) percentage = 100;
|
||||
$('#progressBar').css('width', percentage+'%');
|
||||
if(percentage>5) $('#progressBar').html(parseInt(percentage)+'%');
|
||||
}
|
||||
else
|
||||
{
|
||||
$('#progressBar').css('width', 0+'%');
|
||||
$('#progressBar').html('');
|
||||
}
|
||||
}
|
||||
|
||||
function updateProfileTable()
|
||||
{
|
||||
var dps = 0;
|
||||
var slope = "";
|
||||
var color = "";
|
||||
|
||||
var html = '<h3>Schedule Points</h3><div class="table-responsive" style="scroll: none"><table class="table table-striped">';
|
||||
html += '<tr><th style="width: 50px">#</th><th>Target Time in ' + time_scale_long+ '</th><th>Target Temperature in °'+temp_scale_display+'</th><th>Slope in °'+temp_scale_display+'/'+time_scale_slope+'</th><th></th></tr>';
|
||||
|
||||
for(var i=0; i<graph.profile.data.length;i++)
|
||||
{
|
||||
|
||||
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 += '<tr><td><h4>' + (i+1) + '</h4></td>';
|
||||
html += '<td><input type="text" class="form-control" id="profiletable-0-'+i+'" value="'+ timeProfileFormatter(graph.profile.data[i][0],true) + '" style="width: 60px" /></td>';
|
||||
html += '<td><input type="text" class="form-control" id="profiletable-1-'+i+'" value="'+ graph.profile.data[i][1] + '" style="width: 60px" /></td>';
|
||||
html += '<td><div class="input-group"><span class="glyphicon glyphicon-circle-arrow-' + slope + ' input-group-addon ds-trend" style="background: '+color+'"></span><input type="text" class="form-control ds-input" readonly value="' + formatDPS(dps) + '" style="width: 100px" /></div></td>';
|
||||
html += '<td> </td></tr>';
|
||||
}
|
||||
|
||||
html += '</table></div>';
|
||||
|
||||
$('#profile_table').html(html);
|
||||
|
||||
//Link table to graph
|
||||
$(".form-control").change(function(e)
|
||||
{
|
||||
var id = $(this)[0].id; //e.currentTarget.attributes.id
|
||||
var value = parseInt($(this)[0].value);
|
||||
var fields = id.split("-");
|
||||
var col = parseInt(fields[1]);
|
||||
var row = parseInt(fields[2]);
|
||||
|
||||
if (graph.profile.data.length > 0) {
|
||||
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 (1500 * 9 / 5) + 32
|
||||
}
|
||||
else {
|
||||
return 1500
|
||||
}
|
||||
}
|
||||
|
||||
function timeTickFormatter(val)
|
||||
{
|
||||
if (val < 1800)
|
||||
{
|
||||
return val;
|
||||
}
|
||||
else
|
||||
{
|
||||
var hours = Math.floor(val / (3600));
|
||||
var div_min = val % (3600);
|
||||
var minutes = Math.floor(div_min / 60);
|
||||
|
||||
if (hours < 10) {hours = "0"+hours;}
|
||||
if (minutes < 10) {minutes = "0"+minutes;}
|
||||
|
||||
return hours+":"+minutes;
|
||||
}
|
||||
}
|
||||
|
||||
function runTask()
|
||||
{
|
||||
var cmd =
|
||||
{
|
||||
"cmd": "RUN",
|
||||
"profile": profiles[selected_profile]
|
||||
}
|
||||
|
||||
graph.live.data = [];
|
||||
graph.plot = $.plot("#graph_container", [ graph.profile, graph.live ] , getOptions());
|
||||
|
||||
ws_control.send(JSON.stringify(cmd));
|
||||
|
||||
}
|
||||
|
||||
function runTaskSimulation()
|
||||
{
|
||||
var cmd =
|
||||
{
|
||||
"cmd": "SIMULATE",
|
||||
"profile": profiles[selected_profile]
|
||||
}
|
||||
|
||||
graph.live.data = [];
|
||||
graph.plot = $.plot("#graph_container", [ graph.profile, graph.live ] , getOptions());
|
||||
|
||||
ws_control.send(JSON.stringify(cmd));
|
||||
|
||||
}
|
||||
|
||||
|
||||
function abortTask()
|
||||
{
|
||||
var cmd = {"cmd": "STOP"};
|
||||
ws_control.send(JSON.stringify(cmd));
|
||||
}
|
||||
|
||||
function enterNewMode()
|
||||
{
|
||||
state="EDIT"
|
||||
$('#status').slideUp();
|
||||
$('#edit').show();
|
||||
$('#profile_selector').hide();
|
||||
$('#btn_controls').hide();
|
||||
$('#form_profile_name').attr('value', '');
|
||||
$('#form_profile_name').attr('placeholder', 'Please enter a name');
|
||||
graph.profile.points.show = true;
|
||||
graph.profile.draggable = true;
|
||||
graph.profile.data = [];
|
||||
graph.plot = $.plot("#graph_container", [ graph.profile, graph.live ], getOptions());
|
||||
updateProfileTable();
|
||||
}
|
||||
|
||||
function enterEditMode()
|
||||
{
|
||||
state="EDIT"
|
||||
$('#status').slideUp();
|
||||
$('#edit').show();
|
||||
$('#profile_selector').hide();
|
||||
$('#btn_controls').hide();
|
||||
console.log(profiles);
|
||||
$('#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());
|
||||
updateProfileTable();
|
||||
}
|
||||
|
||||
function leaveEditMode()
|
||||
{
|
||||
selected_profile_name = $('#form_profile_name').val();
|
||||
ws_storage.send('GET');
|
||||
state="IDLE";
|
||||
$('#edit').hide();
|
||||
$('#profile_selector').show();
|
||||
$('#btn_controls').show();
|
||||
$('#status').slideDown();
|
||||
$('#profile_table').slideUp();
|
||||
graph.profile.points.show = false;
|
||||
graph.profile.draggable = false;
|
||||
graph.plot = $.plot("#graph_container", [ graph.profile, graph.live ], getOptions());
|
||||
}
|
||||
|
||||
function newPoint()
|
||||
{
|
||||
if(graph.profile.data.length > 0)
|
||||
{
|
||||
var pointx = parseInt(graph.profile.data[graph.profile.data.length-1][0])+15;
|
||||
}
|
||||
else
|
||||
{
|
||||
var pointx = 0;
|
||||
}
|
||||
graph.profile.data.push([pointx, Math.floor((Math.random()*230)+25)]);
|
||||
graph.plot = $.plot("#graph_container", [ graph.profile, graph.live ], getOptions());
|
||||
updateProfileTable();
|
||||
}
|
||||
|
||||
function delPoint()
|
||||
{
|
||||
graph.profile.data.splice(-1,1)
|
||||
graph.plot = $.plot("#graph_container", [ graph.profile, graph.live ], getOptions());
|
||||
updateProfileTable();
|
||||
}
|
||||
|
||||
function toggleTable()
|
||||
{
|
||||
if($('#profile_table').css('display') == 'none')
|
||||
{
|
||||
$('#profile_table').slideDown();
|
||||
}
|
||||
else
|
||||
{
|
||||
$('#profile_table').slideUp();
|
||||
}
|
||||
}
|
||||
|
||||
function saveProfile()
|
||||
{
|
||||
name = $('#form_profile_name').val();
|
||||
var rawdata = graph.plot.getData()[0].data
|
||||
var data = [];
|
||||
var last = -1;
|
||||
|
||||
for(var i=0; i<rawdata.length;i++)
|
||||
{
|
||||
if(rawdata[i][0] > last)
|
||||
{
|
||||
data.push([rawdata[i][0], rawdata[i][1]]);
|
||||
}
|
||||
else
|
||||
{
|
||||
$.bootstrapGrowl("<span class=\"glyphicon glyphicon-exclamation-sign\"></span> <b>ERROR 88:</b><br/>An oven is not a time-machine", {
|
||||
ele: 'body', // which element to append to
|
||||
type: 'alert', // (null, 'info', 'error', 'success')
|
||||
offset: {from: 'top', amount: 250}, // 'top', or 'bottom'
|
||||
align: 'center', // ('left', 'right', or 'center')
|
||||
width: 385, // (integer, or 'auto')
|
||||
delay: 5000,
|
||||
allow_dismiss: true,
|
||||
stackup_spacing: 10 // spacing between consecutively stacked growls.
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
last = rawdata[i][0];
|
||||
}
|
||||
|
||||
var profile = { "type": "profile", "data": data, "name": name }
|
||||
var put = { "cmd": "PUT", "profile": profile }
|
||||
|
||||
var put_cmd = JSON.stringify(put);
|
||||
|
||||
ws_storage.send(put_cmd);
|
||||
|
||||
leaveEditMode();
|
||||
}
|
||||
|
||||
function getOptions()
|
||||
{
|
||||
|
||||
var options =
|
||||
{
|
||||
|
||||
series:
|
||||
{
|
||||
lines:
|
||||
{
|
||||
show: true
|
||||
},
|
||||
|
||||
points:
|
||||
{
|
||||
show: true,
|
||||
radius: 5,
|
||||
symbol: "circle"
|
||||
},
|
||||
|
||||
shadowSize: 3
|
||||
|
||||
},
|
||||
|
||||
xaxis:
|
||||
{
|
||||
min: 0,
|
||||
tickColor: 'rgba(216, 211, 197, 0.2)',
|
||||
tickFormatter: timeTickFormatter,
|
||||
font:
|
||||
{
|
||||
size: 14,
|
||||
lineHeight: 14, weight: "normal",
|
||||
family: "Digi",
|
||||
variant: "small-caps",
|
||||
color: "rgba(216, 211, 197, 0.85)"
|
||||
}
|
||||
},
|
||||
|
||||
yaxis:
|
||||
{
|
||||
min: 0,
|
||||
tickDecimals: 0,
|
||||
draggable: false,
|
||||
tickColor: 'rgba(216, 211, 197, 0.2)',
|
||||
font:
|
||||
{
|
||||
size: 14,
|
||||
lineHeight: 14,
|
||||
weight: "normal",
|
||||
family: "Digi",
|
||||
variant: "small-caps",
|
||||
color: "rgba(216, 211, 197, 0.85)"
|
||||
}
|
||||
},
|
||||
|
||||
grid:
|
||||
{
|
||||
color: 'rgba(216, 211, 197, 0.55)',
|
||||
borderWidth: 1,
|
||||
labelMargin: 10,
|
||||
mouseActiveRadius: 50
|
||||
},
|
||||
|
||||
legend:
|
||||
{
|
||||
show: false
|
||||
}
|
||||
}
|
||||
|
||||
return options;
|
||||
|
||||
}
|
||||
|
||||
function MARK_SWITCH_TO_CHEMATEX() {
|
||||
// I want to run the script /home/pi/mark_scripts/chematex then alert:
|
||||
|
||||
if (confirm('Switching to CHEMATEX kiln.\nCAUTION! This will cancel any current kiln firing!\nClick OK then wait 10 seconds before refreshing this page.')){
|
||||
var cmd =
|
||||
{
|
||||
"cmd": "MARK_SWITCH_TO_CHEMATEX",
|
||||
}
|
||||
ws_control.send(JSON.stringify(cmd));
|
||||
}
|
||||
else {
|
||||
alert ("Oven Switchover Canceled")
|
||||
}
|
||||
}
|
||||
|
||||
function MARK_SWITCH_TO_RHODE() {
|
||||
// I want to run the script /home/pi/mark_scripts/rhode then alert:
|
||||
|
||||
if (confirm('Switching to RHODE kiln.\nCAUTION! This will cancel any current kiln firing!\nClick OK then wait 10 seconds before refreshing this page.')){
|
||||
var cmd =
|
||||
{
|
||||
"cmd": "MARK_SWITCH_TO_RHODE",
|
||||
}
|
||||
ws_control.send(JSON.stringify(cmd));
|
||||
}
|
||||
else {
|
||||
alert ("Oven Switchover Canceled")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$(document).ready(function()
|
||||
{
|
||||
|
||||
if(!("WebSocket" in window))
|
||||
{
|
||||
$('#chatLog, input, button, #examples').fadeOut("fast");
|
||||
$('<p>Oh no, you need a browser that supports WebSockets. How about <a href="http://www.google.com/chrome">Google Chrome</a>?</p>').appendTo('#container');
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
// Status Socket ////////////////////////////////
|
||||
|
||||
ws_status.onopen = function()
|
||||
{
|
||||
console.log("Status Socket has been opened");
|
||||
|
||||
$.bootstrapGrowl("<span class=\"glyphicon glyphicon-exclamation-sign\"></span>Getting data from server",
|
||||
{
|
||||
ele: 'body', // which element to append to
|
||||
type: 'success', // (null, 'info', 'error', 'success')
|
||||
offset: {from: 'top', amount: 250}, // 'top', or 'bottom'
|
||||
align: 'center', // ('left', 'right', or 'center')
|
||||
width: 385, // (integer, or 'auto')
|
||||
delay: 2500,
|
||||
allow_dismiss: true,
|
||||
stackup_spacing: 10 // spacing between consecutively stacked growls.
|
||||
});
|
||||
};
|
||||
|
||||
ws_status.onclose = function()
|
||||
{
|
||||
$.bootstrapGrowl("<span class=\"glyphicon glyphicon-exclamation-sign\"></span> <b>ERROR 1:</b><br/>Status Websocket not available", {
|
||||
ele: 'body', // which element to append to
|
||||
type: 'error', // (null, 'info', 'error', 'success')
|
||||
offset: {from: 'top', amount: 250}, // 'top', or 'bottom'
|
||||
align: 'center', // ('left', 'right', or 'center')
|
||||
width: 385, // (integer, or 'auto')
|
||||
delay: 5000,
|
||||
allow_dismiss: true,
|
||||
stackup_spacing: 10 // spacing between consecutively stacked growls.
|
||||
});
|
||||
};
|
||||
|
||||
ws_status.onmessage = function(e)
|
||||
{
|
||||
console.log("received status data")
|
||||
console.log(e.data);
|
||||
|
||||
x = JSON.parse(e.data);
|
||||
if (x.type == "backlog")
|
||||
{
|
||||
if (x.profile)
|
||||
{
|
||||
selected_profile_name = x.profile.name;
|
||||
$.each(profiles, function(i,v) {
|
||||
if(v.name == x.profile.name) {
|
||||
updateProfile(i);
|
||||
$('#e2').select2('val', i);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$.each(x.log, function(i,v) {
|
||||
graph.live.data.push([v.runtime, v.temperature]);
|
||||
graph.plot = $.plot("#graph_container", [ graph.profile, graph.live ] , getOptions());
|
||||
});
|
||||
}
|
||||
|
||||
if(state!="EDIT")
|
||||
{
|
||||
state = x.state;
|
||||
|
||||
if (state!=state_last)
|
||||
{
|
||||
if(state_last == "RUNNING")
|
||||
{
|
||||
$('#target_temp').html('---');
|
||||
updateProgress(0);
|
||||
$.bootstrapGrowl("<span class=\"glyphicon glyphicon-exclamation-sign\"></span> <b>Run completed</b>", {
|
||||
ele: 'body', // which element to append to
|
||||
type: 'success', // (null, 'info', 'error', 'success')
|
||||
offset: {from: 'top', amount: 250}, // 'top', or 'bottom'
|
||||
align: 'center', // ('left', 'right', or 'center')
|
||||
width: 385, // (integer, or 'auto')
|
||||
delay: 0,
|
||||
allow_dismiss: true,
|
||||
stackup_spacing: 10 // spacing between consecutively stacked growls.
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if(state=="RUNNING")
|
||||
{
|
||||
$("#nav_start").hide();
|
||||
$("#nav_stop").show();
|
||||
|
||||
graph.live.data.push([x.runtime, x.temperature]);
|
||||
graph.plot = $.plot("#graph_container", [ graph.profile, graph.live ] , getOptions());
|
||||
|
||||
left = parseInt(x.totaltime-x.runtime);
|
||||
eta = new Date(left * 1000).toISOString().substr(11, 8);
|
||||
|
||||
updateProgress(parseFloat(x.runtime)/parseFloat(x.totaltime)*100);
|
||||
$('#state').html('<span class="glyphicon glyphicon-time" style="font-size: 22px; font-weight: normal"></span><span style="font-family: Digi; font-size: 40px;">' + eta + '</span>');
|
||||
$('#target_temp').html(parseInt(x.target));
|
||||
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
$("#nav_start").show();
|
||||
$("#nav_stop").hide();
|
||||
$('#state').html('<p class="ds-text">'+state+'</p>');
|
||||
}
|
||||
|
||||
$('#act_temp').html(parseInt(x.temperature));
|
||||
|
||||
if (x.heat > 0.0) {
|
||||
setTimeout(function() { $('#heat').addClass("ds-led-heat-active") }, 0 )
|
||||
setTimeout(function() { $('#heat').removeClass("ds-led-heat-active") }, (x.heat*1000.0)-5)
|
||||
}
|
||||
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 > 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;
|
||||
oven_kw = x.oven_kw;
|
||||
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()
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
ws_control.onmessage = function(e)
|
||||
{
|
||||
//Data from Simulation
|
||||
console.log ("control socket has been opened")
|
||||
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());
|
||||
|
||||
}
|
||||
|
||||
// Storage Socket ///////////////////////////////
|
||||
|
||||
ws_storage.onopen = function()
|
||||
{
|
||||
ws_storage.send('GET');
|
||||
};
|
||||
|
||||
|
||||
ws_storage.onmessage = function(e)
|
||||
{
|
||||
message = JSON.parse(e.data);
|
||||
|
||||
if(message.resp)
|
||||
{
|
||||
if(message.resp == "FAIL")
|
||||
{
|
||||
if (confirm('Overwrite?'))
|
||||
{
|
||||
message.force=true;
|
||||
console.log("Sending: " + JSON.stringify(message));
|
||||
ws_storage.send(JSON.stringify(message));
|
||||
}
|
||||
else
|
||||
{
|
||||
//do nothing
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//the message is an array of profiles
|
||||
//FIXME: this should be better, maybe a {"profiles": ...} container?
|
||||
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<profiles.length; i++)
|
||||
{
|
||||
var profile = profiles[i];
|
||||
//console.log(profile.name);
|
||||
$('#e2').append('<option value="'+i+'">'+profile.name+'</option>');
|
||||
|
||||
if (profile.name == selected_profile_name)
|
||||
{
|
||||
selected_profile = i;
|
||||
$('#e2').select2('val', i);
|
||||
updateProfile(i);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
$("#e2").select2(
|
||||
{
|
||||
placeholder: "Select Profile",
|
||||
allowClear: true,
|
||||
minimumResultsForSearch: -1
|
||||
});
|
||||
|
||||
|
||||
$("#e2").on("change", function(e)
|
||||
{
|
||||
updateProfile(e.val);
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
@ -1,3 +1,4 @@
|
||||
oven_kw = x.oven_kw;
|
||||
var state = "IDLE";
|
||||
var state_last = "";
|
||||
var graph = [ 'profile', 'live'];
|
||||
@ -51,7 +52,7 @@ function updateProfile(id)
|
||||
selected_profile = id;
|
||||
selected_profile_name = profiles[id].name;
|
||||
var job_seconds = profiles[id].data.length === 0 ? 0 : parseInt(profiles[id].data[profiles[id].data.length-1][0]);
|
||||
var kwh = (3850*job_seconds/3600/1000).toFixed(2);
|
||||
var kwh = (oven_kw*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);
|
||||
@ -440,6 +441,35 @@ function getOptions()
|
||||
|
||||
}
|
||||
|
||||
function MARK_SWITCH_TO_CHEMATEX() {
|
||||
// I want to run the script /home/pi/mark_scripts/chematex then alert:
|
||||
|
||||
if (confirm('Switching to CHEMATEX kiln.\nCAUTION! This will cancel any current kiln firing!\nClick OK then wait 10 seconds before refreshing this page.')){
|
||||
var cmd =
|
||||
{
|
||||
"cmd": "MARK_SWITCH_TO_CHEMATEX",
|
||||
}
|
||||
ws_control.send(JSON.stringify(cmd));
|
||||
}
|
||||
else {
|
||||
alert ("Oven Switchover Canceled")
|
||||
}
|
||||
}
|
||||
|
||||
function MARK_SWITCH_TO_RHODE() {
|
||||
// I want to run the script /home/pi/mark_scripts/rhode then alert:
|
||||
|
||||
if (confirm('Switching to RHODE kiln.\nCAUTION! This will cancel any current kiln firing!\nClick OK then wait 10 seconds before refreshing this page.')){
|
||||
var cmd =
|
||||
{
|
||||
"cmd": "MARK_SWITCH_TO_RHODE",
|
||||
}
|
||||
ws_control.send(JSON.stringify(cmd));
|
||||
}
|
||||
else {
|
||||
alert ("Oven Switchover Canceled")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$(document).ready(function()
|
||||
|
||||
161
public/control.html
Normal file
161
public/control.html
Normal file
@ -0,0 +1,161 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
|
||||
<title>Chematex Kiln Controller</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<script src="assets/js/jquery-1.10.2.min.js"></script>
|
||||
<script src="assets/js/jquery.event.drag-2.2.js"></script>
|
||||
<script src="assets/js/jquery.flot.js"></script>
|
||||
<script src="assets/js/jquery.flot.resize.js"></script>
|
||||
<script src="assets/js/jquery.flot.draggable.js"></script>
|
||||
<script src="assets/js/bootstrap.min.js"></script>
|
||||
<script src="assets/js/jquery.bootstrap-growl.min.js"></script>
|
||||
<script src="assets/js/select2.min.js"></script>
|
||||
<script src="assets/js/picoreflow.js"></script>
|
||||
|
||||
<link rel="stylesheet" href="assets/css/bootstrap.min.css"/>
|
||||
<link rel="stylesheet" href="assets/css/bootstrap-theme.min.css"/>
|
||||
<link rel="stylesheet" href="assets/css/bootstrap-modal.css"/>
|
||||
<link rel="stylesheet" href="assets/css/select2.css"/>
|
||||
<link rel="stylesheet" href="assets/css/picoreflow.css"/>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<br><center><font size = "+3" color = "green">CHEMATEX Ugnen - Drejstuga Fun!</font></center>
|
||||
<center><font color = "blue" size = "+2">Ensure switches and power cord are in proper positions</font></center>
|
||||
<center><a href="http://192.168.0.12:8081/picoreflow/control.html" onClick="MARK_SWITCH_TO_RHODE();">Byt till Rhode</a></center>
|
||||
|
||||
<div class="container">
|
||||
<div id="status">
|
||||
<div class="ds-title-panel">
|
||||
<div class="ds-title">Sensor Temp</div>
|
||||
<div class="ds-title">Target Temp</div>
|
||||
<div class="ds-title ds-state pull-right" style="border-left: 1px solid #ccc;"> Heat Cool Air Hazard Door </div>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
<div class="ds-panel">
|
||||
<div class="display ds-num"><span id="act_temp">25</span><span class="ds-unit" id="act_temp_scale" >°C</span></div>
|
||||
<div class="display ds-num ds-target"><span id="target_temp">---</span><span class="ds-unit" id="target_temp_scale">°C</span></div>
|
||||
<div class="display ds-num ds-text" id="state"></div>
|
||||
<div class="display pull-right ds-state" style="padding-right:0"><span class="ds-led" id="heat">\</span><span class="ds-led" id="cool">l</span><span class="ds-led" id="air">[</span><span class="ds-led" id="hazard">I</span><span class="ds-led" id="door">♨</span></div>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
<div>
|
||||
<div class="progress progress-striped active">
|
||||
<div id="progressBar" class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%">
|
||||
<span class="sr-only"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<div id="profile_selector" class="pull-left">
|
||||
<select id="e2" class="select2" style="margin-top: 4px"></select>
|
||||
<button id="btn_edit" type="button" class="btn btn-default" onclick="enterEditMode()"><span class="glyphicon glyphicon-edit"></span></button>
|
||||
<button id="btn_new" type="button" class="btn btn-default" onclick="enterNewMode(selected_profile)"><span class="glyphicon glyphicon-plus"></span></button>
|
||||
</div>
|
||||
<div id="btn_controls" class="pull-right" style="margin-top: 3px">
|
||||
<div id="nav_start" class="btn-group" style="display:none">
|
||||
<button type="button" class="btn btn-default" style="visibility: hidden;" onclick="runTaskSimulation();">Simulate</button>
|
||||
<button type="button" class="btn btn-success" data-toggle="modal" data-target="#jobSummaryModal"><span class="glyphicon glyphicon-play"></span> Start</button>
|
||||
</div>
|
||||
<button id="nav_stop" type="button" class="btn btn-danger" onclick="abortTask()" style="display:none" ><span class="glyphicon glyphicon-stop"></span> Stop</button>
|
||||
</div>
|
||||
<div id="edit" style="display:none;">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon">Schedule Name</span>
|
||||
<input id="form_profile_name" type="text" class="form-control" />
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-success" type="button" onclick="saveProfile();">Save</button>
|
||||
<button id="btn_exit" type="button" class="btn btn-default" onclick="leaveEditMode()"><span class="glyphicon glyphicon-remove"></span></button>
|
||||
</span>
|
||||
</div>
|
||||
<div class="btn-group btn-group-sm" style="margin-top: 10px">
|
||||
<button id="btn_newPoint" type="button" class="btn btn-default" onclick="newPoint()"><span class="glyphicon glyphicon-plus"></span></button>
|
||||
<button id="btn_delPoint" type="button" class="btn btn-default" onclick="delPoint()"><span class="glyphicon glyphicon-minus"></span></button>
|
||||
</div>
|
||||
<div class="btn-group btn-group-sm" style="margin-top: 10px">
|
||||
<button id="btn_table" type="button" class="btn btn-default" onclick="toggleTable()"><span class="glyphicon glyphicon-list"></span></button>
|
||||
<button id="btn_live" type="button" class="btn btn-default" onclick="toggleLive()"><span class="glyphicon glyphicon-eye-open"></span></button>
|
||||
</div>
|
||||
<div class="btn-group btn-group-sm" style="margin-top: 10px">
|
||||
<button id="btn_delProfile" type="button" class="btn btn-danger" data-toggle="modal" data-target="#delProfileModal"><span class="glyphicon glyphicon-trash"></span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div id="graph_container" class="graph"></div>
|
||||
</div>
|
||||
<div id="profile_table" class="panel-footer" style="display:none;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="jobSummaryModal" class="modal fade" tabindex="-1" aria-hidden="true" style="display: none;">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h3 class="modal-title" id="jobSummaryModalLabel">Task Overview</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<table class="table table-bordered">
|
||||
<tr><td>Selected Profile</td><td><b><span id="sel_prof"></span></b></td></tr>
|
||||
<tr><td>Estimated Runtime</td><td><b><span id="sel_prof_eta"></span></b></td></tr>
|
||||
<tr><td>Estimated Power consumption</td><td><b><span id="sel_prof_cost"></span></b></td></tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="btn-group" style="width: 100%">
|
||||
<button type="button" class="btn btn-danger" style="width: 50%" data-dismiss="modal">No, take me back</button>
|
||||
<button type="button" class="btn btn-success" style="width: 50%" data-dismiss="modal" onclick="runTask()">Yes, start the Run</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="delProfileModal" class="modal fade" tabindex="-1" aria-hidden="true" style="display: none;">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h3 class="modal-title" id="delProfileModalLabel">Delete this profile?</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
Do your really want to delete this profile?
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="btn-group" style="width: 100%">
|
||||
<button type="button" class="btn btn-danger" style="width: 50%" data-dismiss="modal">No, take me back</button>
|
||||
<button type="button" class="btn btn-success" style="width: 50%" data-dismiss="modal" onclick="deleteProfile()">Yes, delete the profile</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="overwriteProfileModal" class="modal fade" tabindex="-1" aria-hidden="true" style="display: none;">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h3 class="modal-title" id="overwriteProfileModalLabel">Overwrite this profile?</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
Do your really want to overwrite this profile?
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="btn-group" style="width: 100%">
|
||||
<button type="button" class="btn btn-danger" style="width: 50%" data-dismiss="modal">No, take me back</button>
|
||||
<button type="button" class="btn btn-success" style="width: 50%" data-dismiss="modal" onclick="deleteProfile()">Yes, delete the profile</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@ -2,7 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
|
||||
<title>Kiln Controller</title>
|
||||
<title>Drejstuga Fun!</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<script src="assets/js/jquery-1.10.2.min.js"></script>
|
||||
@ -23,13 +23,14 @@
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<br><center><font size = "+3" color = "green">Drejstuga Fun!</font></center>
|
||||
|
||||
<div class="container">
|
||||
<div id="status">
|
||||
<div class="ds-title-panel">
|
||||
<div class="ds-title">Sensor Temp</div>
|
||||
<div class="ds-title">Target Temp</div>
|
||||
<div class="ds-title ds-state pull-right" style="border-left: 1px solid #ccc;">Status</div>
|
||||
<div class="ds-title ds-state pull-right" style="border-left: 1px solid #ccc;"> Heat Cool Air Hazard Door </div>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
<div class="ds-panel">
|
||||
@ -50,36 +51,23 @@
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<div id="profile_selector" class="pull-left">
|
||||
<select id="e2" class="select2" style="margin-top: 4px"></select>
|
||||
<button id="btn_edit" type="button" class="btn btn-default" onclick="enterEditMode()"><span class="glyphicon glyphicon-edit"></span></button>
|
||||
<button id="btn_new" type="button" class="btn btn-default" onclick="enterNewMode(selected_profile)"><span class="glyphicon glyphicon-plus"></span></button>
|
||||
</div>
|
||||
<div id="btn_controls" class="pull-right" style="margin-top: 3px">
|
||||
<div id="nav_start" class="btn-group" style="display:none">
|
||||
<button type="button" class="btn btn-default" style="visibility: hidden;" onclick="runTaskSimulation();">Simulate</button>
|
||||
<button type="button" class="btn btn-success" data-toggle="modal" data-target="#jobSummaryModal"><span class="glyphicon glyphicon-play"></span> Start</button>
|
||||
</div>
|
||||
<button id="nav_stop" type="button" class="btn btn-danger" onclick="abortTask()" style="display:none" ><span class="glyphicon glyphicon-stop"></span> Stop</button>
|
||||
</div>
|
||||
<div id="edit" style="display:none;">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon">Schedule Name</span>
|
||||
<input id="form_profile_name" type="text" class="form-control" />
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-success" type="button" onclick="saveProfile();">Save</button>
|
||||
<button id="btn_exit" type="button" class="btn btn-default" onclick="leaveEditMode()"><span class="glyphicon glyphicon-remove"></span></button>
|
||||
</span>
|
||||
</div>
|
||||
<div class="btn-group btn-group-sm" style="margin-top: 10px">
|
||||
<button id="btn_newPoint" type="button" class="btn btn-default" onclick="newPoint()"><span class="glyphicon glyphicon-plus"></span></button>
|
||||
<button id="btn_delPoint" type="button" class="btn btn-default" onclick="delPoint()"><span class="glyphicon glyphicon-minus"></span></button>
|
||||
</div>
|
||||
<div class="btn-group btn-group-sm" style="margin-top: 10px">
|
||||
<button id="btn_table" type="button" class="btn btn-default" onclick="toggleTable()"><span class="glyphicon glyphicon-list"></span></button>
|
||||
<button id="btn_live" type="button" class="btn btn-default" onclick="toggleLive()"><span class="glyphicon glyphicon-eye-open"></span></button>
|
||||
</div>
|
||||
<div class="btn-group btn-group-sm" style="margin-top: 10px">
|
||||
<button id="btn_delProfile" type="button" class="btn btn-danger" data-toggle="modal" data-target="#delProfileModal"><span class="glyphicon glyphicon-trash"></span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -94,7 +82,6 @@
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h3 class="modal-title" id="jobSummaryModalLabel">Task Overview</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
@ -106,8 +93,6 @@
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="btn-group" style="width: 100%">
|
||||
<button type="button" class="btn btn-danger" style="width: 50%" data-dismiss="modal">No, take me back</button>
|
||||
<button type="button" class="btn btn-success" style="width: 50%" data-dismiss="modal" onclick="runTask()">Yes, start the Run</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -118,7 +103,6 @@
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h3 class="modal-title" id="delProfileModalLabel">Delete this profile?</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
@ -126,8 +110,6 @@
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="btn-group" style="width: 100%">
|
||||
<button type="button" class="btn btn-danger" style="width: 50%" data-dismiss="modal">No, take me back</button>
|
||||
<button type="button" class="btn btn-success" style="width: 50%" data-dismiss="modal" onclick="deleteProfile()">Yes, delete the profile</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -138,7 +120,6 @@
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h3 class="modal-title" id="overwriteProfileModalLabel">Overwrite this profile?</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
@ -146,8 +127,6 @@
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="btn-group" style="width: 100%">
|
||||
<button type="button" class="btn btn-danger" style="width: 50%" data-dismiss="modal">No, take me back</button>
|
||||
<button type="button" class="btn btn-success" style="width: 50%" data-dismiss="modal" onclick="deleteProfile()">Yes, delete the profile</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
1
storage/profiles/Unni Stengodsglasyr.json
Normal file
1
storage/profiles/Unni Stengodsglasyr.json
Normal file
@ -0,0 +1 @@
|
||||
{"type": "profile", "data": [[0, 20], [1800, 90], [12000, 650], [19200, 1260], [20400, 1260], [21600, 593], [63000, 25]], "name": "Unni Stengodsglasyr"}
|
||||
@ -1 +0,0 @@
|
||||
{"data": [[0, 65], [600, 200], [7500, 250], [14340, 600], [24840, 1300], [45840, 1650], [46800, 1708], [52800, 1888], [54600, 1888]], "type": "profile", "name": "cone-05-long-bisque"}
|
||||
@ -1 +0,0 @@
|
||||
{"data": [[0, 65], [600, 200], [7200, 250], [25200, 1976], [32880, 2232], [33480, 2232], [36780, 1832], [48780, 1400]], "type": "profile", "name": "cone-6-long-glaze"}
|
||||
@ -1 +0,0 @@
|
||||
{"data": [[0, 200], [1740, 200], [1800, 250], [3600, 250]], "type": "profile", "name": "test-200-250"}
|
||||
Loading…
Reference in New Issue
Block a user