sync
This commit is contained in:
parent
eb7b4a3060
commit
8521834143
|
@ -1,4 +1,5 @@
|
||||||
*~
|
*~
|
||||||
|
*.pyc
|
||||||
build
|
build
|
||||||
CMakeCache.txt
|
CMakeCache.txt
|
||||||
CMakeFiles
|
CMakeFiles
|
||||||
|
|
|
@ -4,12 +4,34 @@ Lots of help from here:
|
||||||
http://stackoverflow.com/questions/1093598/pyserial-how-to-read-last-line-sent-from-serial-device
|
http://stackoverflow.com/questions/1093598/pyserial-how-to-read-last-line-sent-from-serial-device
|
||||||
"""
|
"""
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
|
||||||
import time
|
import time
|
||||||
import serial
|
import serial
|
||||||
import struct
|
import struct
|
||||||
|
|
||||||
last_received = ''
|
|
||||||
profile = []
|
status = [0, 23, 23, 0, 0, 0]
|
||||||
|
|
||||||
|
oven_connected = False
|
||||||
|
|
||||||
|
profile = [
|
||||||
|
150,
|
||||||
|
200,
|
||||||
|
217,
|
||||||
|
260,
|
||||||
|
480,
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
-1,
|
||||||
|
-6,
|
||||||
|
60,
|
||||||
|
180,
|
||||||
|
60,
|
||||||
|
150,
|
||||||
|
20,
|
||||||
|
40]
|
||||||
|
|
||||||
PI_TS_MIN = 0
|
PI_TS_MIN = 0
|
||||||
PI_TS_MAX = 1
|
PI_TS_MAX = 1
|
||||||
|
@ -17,8 +39,10 @@ PI_TL = 2
|
||||||
PI_TP = 3
|
PI_TP = 3
|
||||||
PI_TIME_MAX = 4
|
PI_TIME_MAX = 4
|
||||||
|
|
||||||
PI_RAMP_UP_MIN = 5
|
PI_TS_RAMP_UP_MIN = 5
|
||||||
PI_RAMP_UP_MAX = 6
|
PI_TS_RAMP_UP_MAX = 6
|
||||||
|
PI_TP_RAMP_UP_MIN = 5
|
||||||
|
PI_TP_RAMP_UP_MAX = 6
|
||||||
PI_RAMP_DOWN_MIN = 7
|
PI_RAMP_DOWN_MIN = 7
|
||||||
PI_RAMP_DOWN_MAX = 8
|
PI_RAMP_DOWN_MAX = 8
|
||||||
|
|
||||||
|
@ -29,19 +53,29 @@ PI_TL_DURATION_MAX = 12
|
||||||
PI_TP_DURATION_MIN = 13
|
PI_TP_DURATION_MIN = 13
|
||||||
PI_TP_DURATION_MAX = 14
|
PI_TP_DURATION_MAX = 14
|
||||||
|
|
||||||
|
def recv_config(ser):
|
||||||
|
global profile
|
||||||
|
ser.write(chr(255))
|
||||||
|
ser.flush()
|
||||||
|
t = ser.read(30)
|
||||||
|
profile = struct.unpack("hhhhhhhhhhhhhhhhh", t)
|
||||||
|
ser.flushInput()
|
||||||
|
|
||||||
|
|
||||||
def receiving(ser):
|
def receiving(ser):
|
||||||
global last_received
|
global status
|
||||||
buffer = ''
|
try:
|
||||||
ser.write(chr(255))
|
time.sleep(2)
|
||||||
|
recv_config(ser)
|
||||||
|
except Exception, e:
|
||||||
|
print e
|
||||||
|
pass
|
||||||
|
|
||||||
|
while 1:
|
||||||
|
ser.write(chr(254))
|
||||||
ser.flush()
|
ser.flush()
|
||||||
profile = struct.unpack("hhhhhhhhhhhhhhh", ser.read(30))
|
status = struct.unpack("hhhhhb", ser.read(11))
|
||||||
ser.flushInput()
|
ser.flushInput()
|
||||||
while 1:
|
|
||||||
ser.write(chr(254))
|
|
||||||
ser.flush()
|
|
||||||
last_received = ser.read(11)
|
|
||||||
print repr(last_received)
|
|
||||||
ser.flushInput()
|
|
||||||
|
|
||||||
|
|
||||||
class SerialData(object):
|
class SerialData(object):
|
||||||
|
@ -57,15 +91,31 @@ class SerialData(object):
|
||||||
Thread(target=receiving, args=(self.ser,)).start()
|
Thread(target=receiving, args=(self.ser,)).start()
|
||||||
|
|
||||||
def next(self):
|
def next(self):
|
||||||
|
global status
|
||||||
if not self.ser:
|
if not self.ser:
|
||||||
return 100 #return anything so we can test when Arduino isn't connected
|
return status[1]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return int(struct.unpack("hhhhhb", last_received)[1])
|
return status[1]
|
||||||
except Exception, e:
|
except Exception:
|
||||||
print e
|
pass
|
||||||
return 0
|
|
||||||
|
|
||||||
|
def send_config(self):
|
||||||
|
if not self.ser:
|
||||||
|
return
|
||||||
|
|
||||||
|
global profile
|
||||||
|
self.ser.write(struct.pack("hhhhhhhhhhhhhhhhh", profile))
|
||||||
|
|
||||||
|
def send_start(self):
|
||||||
|
if not self.ser:
|
||||||
|
return False
|
||||||
|
|
||||||
|
self.ser.write(chr(251))
|
||||||
|
return True
|
||||||
|
|
||||||
|
def connected(self):
|
||||||
|
return self.ser != None
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
if self.ser:
|
if self.ser:
|
||||||
|
|
|
@ -16,8 +16,14 @@ set(ARDUINO_DEFAULT_BOARD atmega328) # Default Board ID, when not specified
|
||||||
set(ARDUINO_DEFAULT_PORT /dev/ttyUSB0) # Default Port, when not specified
|
set(ARDUINO_DEFAULT_PORT /dev/ttyUSB0) # Default Port, when not specified
|
||||||
|
|
||||||
link_directories(/usr/share/arduino/libraries)
|
link_directories(/usr/share/arduino/libraries)
|
||||||
|
#
|
||||||
|
# generate_arduino_library(reflowctl_lib
|
||||||
|
# SRCS oven_control.cpp profile.cpp
|
||||||
|
# HDRS oven_control.h profile.h
|
||||||
|
# BOARD atmega328)
|
||||||
|
|
||||||
generate_arduino_firmware(reflowctl
|
generate_arduino_firmware(reflowctl
|
||||||
SKETCH reflowctl
|
SRCS oven_control.cpp profile.cpp main.cpp
|
||||||
|
HDRS oven_control.h profile.h
|
||||||
PORT /dev/ttyUSB0
|
PORT /dev/ttyUSB0
|
||||||
BOARD atmega328)
|
BOARD atmega328)
|
||||||
|
|
|
@ -1,456 +0,0 @@
|
||||||
#include "oven_control.h"
|
|
||||||
#include <DFR_Key.h>
|
|
||||||
#include <LiquidCrystal.h>
|
|
||||||
#include "profile.h"
|
|
||||||
|
|
||||||
//Pin assignments for SainSmart LCD Keypad Shield
|
|
||||||
LiquidCrystal _lcd(8, 9, 4, 5, 6, 7);
|
|
||||||
DFR_Key _keypad;
|
|
||||||
Profile _profile;
|
|
||||||
|
|
||||||
OvenCtl::OvenCtl() {
|
|
||||||
|
|
||||||
time = 0;
|
|
||||||
temperature = 1;
|
|
||||||
last_temperature = 1;
|
|
||||||
actual_dt = 0;
|
|
||||||
// timestamps of event beginnings/ends
|
|
||||||
Ts_time_start = 0;
|
|
||||||
Ts_time_end = 0;
|
|
||||||
Tl_time_start = 0;
|
|
||||||
Tl_time_end = 0;
|
|
||||||
Tp_time_start = 0;
|
|
||||||
Tp_time_end = 0;
|
|
||||||
|
|
||||||
// thermostat
|
|
||||||
set_min = 0;
|
|
||||||
set_max = 0;
|
|
||||||
set_dt_min = 0;
|
|
||||||
set_dt_max = 0;
|
|
||||||
|
|
||||||
state = 0;
|
|
||||||
error_condition = 0;
|
|
||||||
is_oven_heating = false;
|
|
||||||
|
|
||||||
// ui stuff
|
|
||||||
led_on = false;
|
|
||||||
disable_checks = false;
|
|
||||||
lcd = &_lcd;
|
|
||||||
keypad = &_keypad;
|
|
||||||
profile = &_profile;
|
|
||||||
lcd->begin(16, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
void OvenCtl::reset() {
|
|
||||||
digitalWrite(7, LOW);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void OvenCtl::send_state() {
|
|
||||||
Serial.write(time & 0xff);
|
|
||||||
Serial.write((time>>8) & 0xff);
|
|
||||||
Serial.write(temperature & 0xff);
|
|
||||||
Serial.write((temperature >> 8) & 0xff);
|
|
||||||
Serial.write(last_temperature & 0xff);
|
|
||||||
Serial.write((last_temperature >> 8) & 0xff);
|
|
||||||
Serial.write(state & 0xff);
|
|
||||||
Serial.write((state >> 8) & 0xff);
|
|
||||||
Serial.write(error_condition & 0xff);
|
|
||||||
Serial.write((error_condition >> 8) & 0xff);
|
|
||||||
Serial.write(is_oven_heating);
|
|
||||||
Serial.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
void OvenCtl::send_config() {
|
|
||||||
int tmp;
|
|
||||||
for (int i=0;i < PI_END; i++)
|
|
||||||
{
|
|
||||||
tmp = profile->data[i];
|
|
||||||
Serial.write(tmp & 0xff);
|
|
||||||
Serial.write((tmp >> 8 ) & 0xff);
|
|
||||||
}
|
|
||||||
Serial.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
void OvenCtl::dispatch_input_config(int cmd) {
|
|
||||||
if (cmd == 255)
|
|
||||||
send_config();
|
|
||||||
else if (cmd == 254)
|
|
||||||
recv_config();
|
|
||||||
else if (cmd == 250)
|
|
||||||
reset();
|
|
||||||
else if (cmd == 253)
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
void OvenCtl::recv_config() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void OvenCtl::handle_states() {
|
|
||||||
int cmd = -1;
|
|
||||||
|
|
||||||
if (state > 0)
|
|
||||||
{
|
|
||||||
time++;
|
|
||||||
get_temp();
|
|
||||||
check_dt();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error_condition != 0) {
|
|
||||||
set_error_state();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Serial.available() > 0) {
|
|
||||||
cmd = Serial.read();
|
|
||||||
if (cmd == 255)
|
|
||||||
send_config();
|
|
||||||
else if (cmd == 254)
|
|
||||||
send_state();
|
|
||||||
else if (cmd == 253)
|
|
||||||
recv_config();
|
|
||||||
else if (cmd == 252)
|
|
||||||
reset();
|
|
||||||
else if (cmd == 251)
|
|
||||||
set_start_state();
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (state) {
|
|
||||||
case CONFIG_STATE:
|
|
||||||
if (profile->handle_config_state(lcd, keypad))
|
|
||||||
set_start_state();
|
|
||||||
break;
|
|
||||||
case START_STATE:
|
|
||||||
handle_start_state();
|
|
||||||
break;
|
|
||||||
case PREHEAT_STATE:
|
|
||||||
handle_preheat_state();
|
|
||||||
break;
|
|
||||||
case RAMP_UP_STATE:
|
|
||||||
handle_ramp_up_state();
|
|
||||||
break;
|
|
||||||
case TAL_FIRST_STATE:
|
|
||||||
handle_tal_first_state();
|
|
||||||
break;
|
|
||||||
case PEAK_STATE:
|
|
||||||
handle_peak_state();
|
|
||||||
break;
|
|
||||||
case TAL_SECOND_STATE:
|
|
||||||
Tl_time_end = time;
|
|
||||||
handle_tal_second_state();
|
|
||||||
break;
|
|
||||||
case RAMP_DOWN_STATE:
|
|
||||||
handle_ramp_down_state();
|
|
||||||
break;
|
|
||||||
case END_STATE:
|
|
||||||
handle_end_state();
|
|
||||||
break;
|
|
||||||
case ERROR_STATE:
|
|
||||||
handle_error_state();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
control_oven();
|
|
||||||
if (state > 0) {
|
|
||||||
print_status();
|
|
||||||
delay(1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void OvenCtl::print_status() {
|
|
||||||
if (error_condition == 0) {
|
|
||||||
String tmp("T: ");
|
|
||||||
if (time < 10)
|
|
||||||
tmp += "00";
|
|
||||||
else if (time < 100)
|
|
||||||
tmp += "0";
|
|
||||||
tmp += time;
|
|
||||||
tmp += " Tmp: ";
|
|
||||||
if (temperature < 10)
|
|
||||||
tmp += "00";
|
|
||||||
else if (temperature < 100)
|
|
||||||
tmp += "0";
|
|
||||||
tmp += temperature;
|
|
||||||
lcd->setCursor(0, 0);
|
|
||||||
lcd->print(tmp);
|
|
||||||
|
|
||||||
tmp = "Profile: ";
|
|
||||||
tmp += state;
|
|
||||||
tmp += "/";
|
|
||||||
tmp += END_STATE;
|
|
||||||
lcd->setCursor(0, 1);
|
|
||||||
lcd->print(tmp);
|
|
||||||
lcd->setCursor(13, 1);
|
|
||||||
if (is_oven_heating)
|
|
||||||
lcd->print("on ");
|
|
||||||
else
|
|
||||||
lcd->print("off");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
lcd->clear();
|
|
||||||
lcd->print("Error:");
|
|
||||||
lcd->setCursor(0, 1);
|
|
||||||
if (error_condition & E_DT_MIN)
|
|
||||||
lcd->print("K/s too low");
|
|
||||||
if (error_condition & E_DT_MAX)
|
|
||||||
lcd->print("K/s too high");
|
|
||||||
if (error_condition & E_TIME_MAX)
|
|
||||||
lcd->print("reflow too long");
|
|
||||||
if (error_condition & E_TS_TOO_SHORT)
|
|
||||||
lcd->print("ts too short");
|
|
||||||
if (error_condition & E_TS_TOO_LONG)
|
|
||||||
lcd->print("ts too long");
|
|
||||||
if (error_condition & E_TL_TOO_SHORT)
|
|
||||||
lcd->print("tal too short");
|
|
||||||
if (error_condition & E_TL_TOO_LONG)
|
|
||||||
lcd->print("tal too long");
|
|
||||||
if (error_condition & E_TP_TOO_LONG)
|
|
||||||
lcd->print("peak too short");
|
|
||||||
if (error_condition & E_TP_TOO_SHORT)
|
|
||||||
lcd->print("peak too long");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void OvenCtl::control_oven() {
|
|
||||||
if (temperature < set_min && !is_oven_heating) {
|
|
||||||
is_oven_heating = true;
|
|
||||||
// Serial.println("Oven turned on");
|
|
||||||
}
|
|
||||||
else if (temperature > set_min && is_oven_heating) {
|
|
||||||
is_oven_heating = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void OvenCtl::set_temp(int min, int max, int dt_min, int dt_max) {
|
|
||||||
set_min = min;
|
|
||||||
set_max = max;
|
|
||||||
set_dt_min = dt_min;
|
|
||||||
set_dt_max = dt_max;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void OvenCtl::get_temp() {
|
|
||||||
last_temperature = temperature;
|
|
||||||
temperature = int(float(analogRead(2)) * 0.2929);
|
|
||||||
actual_dt = temperature - last_temperature;
|
|
||||||
}
|
|
||||||
|
|
||||||
void OvenCtl::check_dt() {
|
|
||||||
if (disable_checks)
|
|
||||||
return;
|
|
||||||
if (actual_dt > set_dt_max) {
|
|
||||||
error_condition |= E_DT_MAX;
|
|
||||||
}
|
|
||||||
if (actual_dt < set_dt_min) {
|
|
||||||
error_condition |= E_DT_MIN;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void OvenCtl::check_max_duration() {
|
|
||||||
if (disable_checks)
|
|
||||||
return;
|
|
||||||
if (time > profile->data[PI_TIME_MAX]) {
|
|
||||||
error_condition |= E_TIME_MAX;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void OvenCtl::check_Ts_duration_min() {
|
|
||||||
if (disable_checks)
|
|
||||||
return;
|
|
||||||
Tl_time_end = time;
|
|
||||||
if (time - Tl_time_start < profile->data[PI_TL_DURATION_MIN]) {
|
|
||||||
error_condition |= E_TL_TOO_SHORT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void OvenCtl::check_Ts_duration_max() {
|
|
||||||
if (disable_checks)
|
|
||||||
return;
|
|
||||||
if (time - Ts_time_start > profile->data[PI_TL_DURATION_MAX]) {
|
|
||||||
error_condition |= E_TS_TOO_LONG;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void OvenCtl::check_Tl_duration_min() {
|
|
||||||
if (disable_checks)
|
|
||||||
return;
|
|
||||||
Tl_time_end = time;
|
|
||||||
if (time - Tl_time_start < profile->data[PI_TL_DURATION_MIN]) {
|
|
||||||
error_condition |= E_TL_TOO_SHORT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void OvenCtl::check_Tl_duration_max() {
|
|
||||||
if (disable_checks)
|
|
||||||
return;
|
|
||||||
if (time - Tl_time_start > profile->data[PI_TL_DURATION_MAX]) {
|
|
||||||
error_condition |= E_TL_TOO_LONG;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void OvenCtl::check_Tp_duration_min() {
|
|
||||||
Tp_time_end = time;
|
|
||||||
if (time - Tp_time_start < profile->data[PI_TP_DURATION_MIN]) {
|
|
||||||
error_condition |= E_TP_TOO_SHORT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void OvenCtl::check_Tp_duration_max() {
|
|
||||||
if (disable_checks)
|
|
||||||
return;
|
|
||||||
if (time - Tp_time_start > profile->data[PI_TP_DURATION_MAX]) {
|
|
||||||
error_condition |= E_TP_TOO_LONG;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void OvenCtl::set_config_state() {
|
|
||||||
profile->print_config_state_0(lcd);
|
|
||||||
}
|
|
||||||
|
|
||||||
void OvenCtl::set_start_state() {
|
|
||||||
led_on = false;
|
|
||||||
digitalWrite(13, LOW);
|
|
||||||
error_condition = 0;
|
|
||||||
state = START_STATE;
|
|
||||||
get_temp();
|
|
||||||
last_temperature = temperature;
|
|
||||||
actual_dt = temperature - last_temperature;
|
|
||||||
set_temp(profile->data[PI_TP]-5, profile->data[PI_TP], 0, profile->data[PI_RAMP_UP_MAX]);
|
|
||||||
lcd->clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void OvenCtl::set_preheat_state() {
|
|
||||||
// Serial.println("Changing state to PREHEAT_STATE");
|
|
||||||
state++;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void OvenCtl::set_ramp_up_state() {
|
|
||||||
state++;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void OvenCtl::set_tal_first_state() {
|
|
||||||
state++;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void OvenCtl::set_peak_state() {
|
|
||||||
state++;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void OvenCtl::set_tal_second_state() {
|
|
||||||
set_temp(0, 25, -3, -6);
|
|
||||||
state++;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void OvenCtl::set_ramp_down_state() {
|
|
||||||
state++;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void OvenCtl::set_end_state() {
|
|
||||||
state++;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void OvenCtl::set_error_state() {
|
|
||||||
if (state != ERROR_STATE) {
|
|
||||||
set_temp(0, 0, 0, 0);
|
|
||||||
state = ERROR_STATE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void OvenCtl::handle_config_state() {
|
|
||||||
if (profile->handle_config_state(lcd, keypad))
|
|
||||||
state++;
|
|
||||||
}
|
|
||||||
|
|
||||||
void OvenCtl::handle_start_state() {
|
|
||||||
check_max_duration();
|
|
||||||
if (temperature > profile->data[PI_TS_MIN]) {
|
|
||||||
Ts_time_start = time;
|
|
||||||
set_preheat_state();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void OvenCtl::handle_preheat_state() {
|
|
||||||
check_Ts_duration_max();
|
|
||||||
check_max_duration();
|
|
||||||
if (temperature > profile->data[PI_TS_MAX]) {
|
|
||||||
check_Ts_duration_min();
|
|
||||||
set_ramp_up_state();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void OvenCtl::handle_ramp_up_state() {
|
|
||||||
check_max_duration();
|
|
||||||
if (temperature > profile->data[PI_TL]) {
|
|
||||||
Tl_time_start = time;
|
|
||||||
set_tal_first_state();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void OvenCtl::handle_tal_first_state() {
|
|
||||||
check_max_duration();
|
|
||||||
check_Tl_duration_max();
|
|
||||||
if (temperature > profile->data[PI_TP] - 5) {
|
|
||||||
Tp_time_start = time;
|
|
||||||
set_peak_state();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void OvenCtl::handle_peak_state() {
|
|
||||||
check_Tl_duration_max();
|
|
||||||
check_Tp_duration_max();
|
|
||||||
if (time - Tp_time_start > profile->data[PI_TP_DURATION_MAX]) {
|
|
||||||
check_Tp_duration_min();
|
|
||||||
set_tal_second_state();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void OvenCtl::handle_tal_second_state() {
|
|
||||||
check_Tl_duration_max();
|
|
||||||
if (temperature < profile->data[PI_TL]) {
|
|
||||||
check_Tl_duration_min();
|
|
||||||
set_ramp_down_state();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void OvenCtl::handle_ramp_down_state() {
|
|
||||||
if (temperature < profile->data[PI_TS_MIN]) {
|
|
||||||
set_end_state();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void OvenCtl::handle_end_state() {
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void OvenCtl::handle_error_state() {
|
|
||||||
if (led_on) {
|
|
||||||
digitalWrite(13, LOW);
|
|
||||||
led_on = false;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
digitalWrite(13, HIGH);
|
|
||||||
led_on = true;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,122 +0,0 @@
|
||||||
#ifndef _H_OVEN_CTL
|
|
||||||
#define _H_OVEN_CTL
|
|
||||||
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// states
|
|
||||||
#define CONFIG_STATE 0
|
|
||||||
#define START_STATE 1
|
|
||||||
#define PREHEAT_STATE 2
|
|
||||||
#define RAMP_UP_STATE 3
|
|
||||||
#define TAL_FIRST_STATE 4
|
|
||||||
#define PEAK_STATE 5
|
|
||||||
#define TAL_SECOND_STATE 6
|
|
||||||
#define RAMP_DOWN_STATE 7
|
|
||||||
#define END_STATE 8
|
|
||||||
#define ERROR_STATE 9
|
|
||||||
|
|
||||||
// error conditions
|
|
||||||
#define E_DT_MIN 1 // temperature dt too small
|
|
||||||
#define E_DT_MAX 2 // temperature dt too big
|
|
||||||
#define E_TIME_MAX 4 // reflow process does take too long
|
|
||||||
#define E_TS_TOO_SHORT 8 // Ts duration too short
|
|
||||||
#define E_TS_TOO_LONG 16 // Ts duration too long
|
|
||||||
#define E_TL_TOO_SHORT 32 // Tl duration too short
|
|
||||||
#define E_TL_TOO_LONG 64 // Tl duration too long
|
|
||||||
#define E_TP_TOO_SHORT 128 // Tp duration too short
|
|
||||||
#define E_TP_TOO_LONG 256 // Tp duration too long
|
|
||||||
#define E_CONFIG 512 // error happened in config state
|
|
||||||
|
|
||||||
#include <Arduino.h>
|
|
||||||
|
|
||||||
class LiquidCrystal;
|
|
||||||
class DFR_Key;
|
|
||||||
class Profile;
|
|
||||||
|
|
||||||
class OvenCtl {
|
|
||||||
public:
|
|
||||||
|
|
||||||
OvenCtl();
|
|
||||||
void handle_states();
|
|
||||||
void set_config_state();
|
|
||||||
|
|
||||||
private:
|
|
||||||
// system time, timestamps and temperatures from sensors
|
|
||||||
int time; // profile seconds
|
|
||||||
int temperature; // actual oven temp
|
|
||||||
int last_temperature; // last oven temp
|
|
||||||
int actual_dt; // actual difference from last to actual temperatur
|
|
||||||
|
|
||||||
// timestamps of event beginnings/ends
|
|
||||||
int Ts_time_start;
|
|
||||||
int Ts_time_end;
|
|
||||||
int Tl_time_start;
|
|
||||||
int Tl_time_end;
|
|
||||||
int Tp_time_start;
|
|
||||||
int Tp_time_end;
|
|
||||||
|
|
||||||
// thermostat
|
|
||||||
int set_min;
|
|
||||||
int set_max;
|
|
||||||
int set_dt_min;
|
|
||||||
int set_dt_max;
|
|
||||||
|
|
||||||
// ui stuff
|
|
||||||
boolean led_on;
|
|
||||||
boolean disable_checks;
|
|
||||||
|
|
||||||
// state machine
|
|
||||||
unsigned int error_condition;
|
|
||||||
unsigned int state;
|
|
||||||
boolean is_oven_heating;
|
|
||||||
|
|
||||||
LiquidCrystal * lcd;
|
|
||||||
DFR_Key * keypad;
|
|
||||||
Profile * profile;
|
|
||||||
|
|
||||||
void print_status();
|
|
||||||
void control_oven();
|
|
||||||
void set_temp(int, int, int, int);
|
|
||||||
void get_temp();
|
|
||||||
void check_dt();
|
|
||||||
void check_max_duration();
|
|
||||||
|
|
||||||
void set_start_state();
|
|
||||||
void set_preheat_state();
|
|
||||||
void set_tal_first_state();
|
|
||||||
void set_ramp_up_state();
|
|
||||||
void set_peak_state();
|
|
||||||
void set_tal_second_state();
|
|
||||||
void set_ramp_down_state();
|
|
||||||
void set_end_state();
|
|
||||||
void set_error_state();
|
|
||||||
|
|
||||||
void handle_config_state();
|
|
||||||
void handle_start_state();
|
|
||||||
void handle_ramp_up_state();
|
|
||||||
void handle_preheat_state();
|
|
||||||
void handle_tal_first_state();
|
|
||||||
void handle_peak_state();
|
|
||||||
void handle_tal_second_state();
|
|
||||||
void handle_ramp_down_state();
|
|
||||||
void handle_end_state();
|
|
||||||
void handle_error_state();
|
|
||||||
|
|
||||||
void check_Ts_duration_min();
|
|
||||||
void check_Ts_duration_max();
|
|
||||||
void check_Tl_duration_min();
|
|
||||||
void check_Tl_duration_max();
|
|
||||||
void check_Tp_duration_min();
|
|
||||||
void check_Tp_duration_max();
|
|
||||||
|
|
||||||
void send_state();
|
|
||||||
void send_config();
|
|
||||||
void reset();
|
|
||||||
|
|
||||||
void recv_config();
|
|
||||||
|
|
||||||
void dispatch_input_config(int);
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
191
libs/profile.cpp
191
libs/profile.cpp
|
@ -1,191 +0,0 @@
|
||||||
#include "profile.h"
|
|
||||||
#include "oven_control.h"
|
|
||||||
#include <LiquidCrystal.h>
|
|
||||||
#include <DFR_Key.h>
|
|
||||||
|
|
||||||
#define P_TS_MIN 0
|
|
||||||
#define P_TS_MAX 1
|
|
||||||
#define P_TL 2
|
|
||||||
#define P_TP 3
|
|
||||||
#define P_TIME_MAX 4
|
|
||||||
|
|
||||||
// PROFILE TEMP PER SECOND RATES
|
|
||||||
#define P_RAMP_UP_RATE_MIN 5
|
|
||||||
#define P_RAMP_UP_RATE_MAX 6
|
|
||||||
#define P_RAMP_DOWN_MAX 7
|
|
||||||
#define P_RAMP_DOWN_MIN 8
|
|
||||||
|
|
||||||
// PROFILE TEMP DURATIONS
|
|
||||||
#define P_TS_DURATION_MIN 9
|
|
||||||
#define P_TS_DURATION_MAX 10
|
|
||||||
#define P_TL_DURATION_MIN 11
|
|
||||||
#define P_TL_DURATION_MAX 12
|
|
||||||
#define P_TP_DURATION_MIN 13
|
|
||||||
#define P_TP_DURATION_MAX 14
|
|
||||||
#define P_END 15
|
|
||||||
|
|
||||||
|
|
||||||
Profile::Profile() :
|
|
||||||
data({150, // °C
|
|
||||||
200, // °C
|
|
||||||
217, // °C
|
|
||||||
260, // 245-260°C
|
|
||||||
480, // seconds
|
|
||||||
|
|
||||||
// profile temp per second rates
|
|
||||||
0, // not used yet
|
|
||||||
50, // 3°C/second
|
|
||||||
-2, // 2°C/seconds min
|
|
||||||
-6, // 6°C/seconds max
|
|
||||||
|
|
||||||
// profile temp durations
|
|
||||||
60,
|
|
||||||
180,
|
|
||||||
60,
|
|
||||||
150,
|
|
||||||
20,
|
|
||||||
40}),
|
|
||||||
config_index(0),
|
|
||||||
config_state(0),
|
|
||||||
key(NO_KEY) {}
|
|
||||||
|
|
||||||
boolean Profile::handle_config_state(LiquidCrystal * lcd, DFR_Key * keypad) {
|
|
||||||
boolean changed = false;
|
|
||||||
|
|
||||||
key = keypad->getKey();
|
|
||||||
|
|
||||||
switch (config_state) {
|
|
||||||
case 0:
|
|
||||||
if (key == SELECT_KEY) {
|
|
||||||
config_state = 2;
|
|
||||||
}
|
|
||||||
else if (key > 0) {
|
|
||||||
config_state++;
|
|
||||||
print_config_state(lcd);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
switch (key) {
|
|
||||||
case LEFT_KEY:
|
|
||||||
config_index = (config_index-1) % P_END;
|
|
||||||
changed = true;
|
|
||||||
break;
|
|
||||||
case RIGHT_KEY:
|
|
||||||
config_index = (config_index+1) % P_END;
|
|
||||||
changed = true;
|
|
||||||
break;
|
|
||||||
case UP_KEY:
|
|
||||||
data[config_index]++;
|
|
||||||
changed = true;
|
|
||||||
break;
|
|
||||||
case DOWN_KEY:
|
|
||||||
data[config_index]--;
|
|
||||||
changed = true;
|
|
||||||
break;
|
|
||||||
case SELECT_KEY:
|
|
||||||
config_state = 2;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
;
|
|
||||||
}
|
|
||||||
if (changed)
|
|
||||||
print_config_state(lcd);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Profile::print_config_state_0(LiquidCrystal * lcd) {
|
|
||||||
lcd->clear();
|
|
||||||
lcd->setCursor(0, 0);
|
|
||||||
lcd->print("start | config");
|
|
||||||
lcd->setCursor(0, 1);
|
|
||||||
lcd->print("[sel] | [other]");
|
|
||||||
}
|
|
||||||
|
|
||||||
void Profile::print_config_state(LiquidCrystal * lcd) {
|
|
||||||
lcd->clear();
|
|
||||||
switch (config_index) {
|
|
||||||
case P_TS_MIN:
|
|
||||||
lcd->setCursor(0,0);
|
|
||||||
lcd->print("P_TS_MIN: ");
|
|
||||||
lcd->print(data[PI_TS_MIN]);
|
|
||||||
break;
|
|
||||||
case P_TS_MAX:
|
|
||||||
lcd->setCursor(0,0);
|
|
||||||
lcd->print("P_TS_MAX: ");
|
|
||||||
lcd->print(data[PI_TS_MAX]);
|
|
||||||
break;
|
|
||||||
case P_TL:
|
|
||||||
lcd->setCursor(0,0);
|
|
||||||
lcd->print("Tl: ");
|
|
||||||
lcd->print(data[PI_TL]);
|
|
||||||
break;
|
|
||||||
case P_TP:
|
|
||||||
lcd->setCursor(0,0);
|
|
||||||
lcd->print("Tp: ");
|
|
||||||
lcd->print(data[PI_TP]);
|
|
||||||
break;
|
|
||||||
case P_TIME_MAX:
|
|
||||||
lcd->setCursor(0,0);
|
|
||||||
lcd->print("time_max: ");
|
|
||||||
lcd->print(data[PI_TIME_MAX]);
|
|
||||||
break;
|
|
||||||
|
|
||||||
// PROFILE TEMP PER SECOND RATES
|
|
||||||
case P_RAMP_UP_RATE_MIN:
|
|
||||||
lcd->setCursor(0,0);
|
|
||||||
lcd->print("ramp_up_min: ");
|
|
||||||
lcd->print(data[PI_RAMP_UP_MIN]);
|
|
||||||
break;
|
|
||||||
case P_RAMP_UP_RATE_MAX:
|
|
||||||
lcd->setCursor(0,0);
|
|
||||||
lcd->print("ramp_up_max: ");
|
|
||||||
lcd->print(data[PI_RAMP_UP_MAX]);
|
|
||||||
break;
|
|
||||||
case P_RAMP_DOWN_MAX:
|
|
||||||
lcd->setCursor(0,0);
|
|
||||||
lcd->print("ramp_down_min: ");
|
|
||||||
lcd->print(data[PI_RAMP_DOWN_MIN]);
|
|
||||||
break;
|
|
||||||
case P_RAMP_DOWN_MIN:
|
|
||||||
lcd->setCursor(0,0);
|
|
||||||
lcd->print("ramp_down_max: ");
|
|
||||||
lcd->print(data[PI_RAMP_DOWN_MAX]);
|
|
||||||
break;
|
|
||||||
|
|
||||||
// PROFILE TEMP DURATIONS
|
|
||||||
case P_TS_DURATION_MIN:
|
|
||||||
lcd->setCursor(0,0);
|
|
||||||
lcd->print("Ts_duration_min: ");
|
|
||||||
lcd->print(data[PI_TS_DURATION_MIN]);
|
|
||||||
break;
|
|
||||||
case P_TS_DURATION_MAX:
|
|
||||||
lcd->setCursor(0,0);
|
|
||||||
lcd->print("Ts_duration_max: ");
|
|
||||||
lcd->print(data[PI_TS_DURATION_MAX]);
|
|
||||||
break;
|
|
||||||
case P_TL_DURATION_MIN:
|
|
||||||
lcd->setCursor(0,0);
|
|
||||||
lcd->print("Tl_duration_min: ");
|
|
||||||
lcd->print(data[PI_TL_DURATION_MIN]);
|
|
||||||
break;
|
|
||||||
case P_TL_DURATION_MAX:
|
|
||||||
lcd->setCursor(0,0);
|
|
||||||
lcd->print("Tl_duration_max: ");
|
|
||||||
lcd->print(data[PI_TL_DURATION_MAX]);
|
|
||||||
break;
|
|
||||||
case P_TP_DURATION_MIN:
|
|
||||||
lcd->setCursor(0,0);
|
|
||||||
lcd->print("Tp_duration_min: ");
|
|
||||||
lcd->print(data[PI_TP_DURATION_MIN]);
|
|
||||||
break;
|
|
||||||
case P_TP_DURATION_MAX:
|
|
||||||
lcd->setCursor(0,0);
|
|
||||||
lcd->print("Tp_duration_max: ");
|
|
||||||
lcd->print(data[PI_TP_DURATION_MAX]);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
#ifndef _H_PROFILE
|
|
||||||
#define _H_PROFILE
|
|
||||||
|
|
||||||
#include <Arduino.h>
|
|
||||||
|
|
||||||
class LiquidCrystal;
|
|
||||||
class DFR_Key;
|
|
||||||
|
|
||||||
#define PI_TS_MIN 0
|
|
||||||
#define PI_TS_MAX 1
|
|
||||||
#define PI_TL 2
|
|
||||||
#define PI_TP 3
|
|
||||||
#define PI_TIME_MAX 4
|
|
||||||
|
|
||||||
// profile temp per second rates
|
|
||||||
#define PI_RAMP_UP_MIN 5
|
|
||||||
#define PI_RAMP_UP_MAX 6
|
|
||||||
#define PI_RAMP_DOWN_MIN 7
|
|
||||||
#define PI_RAMP_DOWN_MAX 8
|
|
||||||
|
|
||||||
// profile temp durations
|
|
||||||
#define PI_TS_DURATION_MIN 9
|
|
||||||
#define PI_TS_DURATION_MAX 10
|
|
||||||
#define PI_TL_DURATION_MIN 11
|
|
||||||
#define PI_TL_DURATION_MAX 12
|
|
||||||
#define PI_TP_DURATION_MIN 13
|
|
||||||
#define PI_TP_DURATION_MAX 14
|
|
||||||
#define PI_END 15
|
|
||||||
|
|
||||||
|
|
||||||
class Profile {
|
|
||||||
public:
|
|
||||||
int data[15];
|
|
||||||
|
|
||||||
unsigned int config_index;
|
|
||||||
int config_state;
|
|
||||||
int key;
|
|
||||||
|
|
||||||
Profile();
|
|
||||||
boolean handle_config_state(LiquidCrystal * lcd, DFR_Key * keypad);
|
|
||||||
void print_config_state(LiquidCrystal * lcd);
|
|
||||||
void print_config_state_0(LiquidCrystal * lcd);
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
35
libs/ui.h
35
libs/ui.h
|
@ -1,35 +0,0 @@
|
||||||
// #ifndef _H_UI
|
|
||||||
// #define _H_UI
|
|
||||||
//
|
|
||||||
// #include <LiquidCrystal.h>
|
|
||||||
// #include <DFR_Key.h>
|
|
||||||
//
|
|
||||||
// class Profile {
|
|
||||||
// public:
|
|
||||||
// int Ts_min;
|
|
||||||
// int Ts_max;
|
|
||||||
// int Tl;
|
|
||||||
// int Tp;
|
|
||||||
// int time_max;
|
|
||||||
//
|
|
||||||
// // profile temp per second rates
|
|
||||||
// int ramp_up_min;
|
|
||||||
// int ramp_up_max;
|
|
||||||
// int ramp_down_max;
|
|
||||||
// int ramp_down_min;
|
|
||||||
//
|
|
||||||
// // profile temp durations
|
|
||||||
// int Ts_duration_min;
|
|
||||||
// int Ts_duration_max;
|
|
||||||
// int Tl_duration_min;
|
|
||||||
// int Tl_duration_max;
|
|
||||||
// int Tp_duration_min;
|
|
||||||
// int Tp_duration_max;
|
|
||||||
// int config_index;
|
|
||||||
//
|
|
||||||
// Profile();
|
|
||||||
// void handle_config_state(LiquidCrystal & lcd, DFR_Key & keypad);
|
|
||||||
// void print_config_state(LiquidCrystal & lcd);
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// #endif
|
|
|
@ -0,0 +1,251 @@
|
||||||
|
#include "oven_control.h"
|
||||||
|
#include <DFR_Key.h>
|
||||||
|
#include <LiquidCrystal.h>
|
||||||
|
#include "profile.h"
|
||||||
|
|
||||||
|
//Pin assignments for SainSmart LCD Keypad Shield
|
||||||
|
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
|
||||||
|
DFR_Key keypad;
|
||||||
|
Profile profile;
|
||||||
|
|
||||||
|
OvenCtl::OvenCtl() {
|
||||||
|
|
||||||
|
time = 0;
|
||||||
|
temperature = 1;
|
||||||
|
last_temperature = 1;
|
||||||
|
actual_dt = 0.f;
|
||||||
|
|
||||||
|
|
||||||
|
// timestamps of event beginnings/ends
|
||||||
|
Ts_time_start = 0;
|
||||||
|
Ts_time_end = 0;
|
||||||
|
Tl_time_start = 0;
|
||||||
|
Tl_time_end = 0;
|
||||||
|
Tp_time_start = 0;
|
||||||
|
Tp_time_end = 0;
|
||||||
|
|
||||||
|
// thermostat
|
||||||
|
set_min = 0;
|
||||||
|
set_max = 0;
|
||||||
|
set_dt_min = 0;
|
||||||
|
set_dt_max = 0;
|
||||||
|
|
||||||
|
op_state = OP_CONFIG;
|
||||||
|
profile_state = START_STATE;
|
||||||
|
error_condition = 0;
|
||||||
|
is_oven_heating = false;
|
||||||
|
|
||||||
|
actual_hysteresis = ramp_up_hysteresis = 1.0; // s
|
||||||
|
ramp_down_hysteresis = 1.0; // s
|
||||||
|
|
||||||
|
// ui stuff
|
||||||
|
disable_checks = false;
|
||||||
|
lcd.begin(16, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OvenCtl::reset() {
|
||||||
|
digitalWrite(7, LOW);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void OvenCtl::send_state() {
|
||||||
|
Serial.write(time & 0xff);
|
||||||
|
Serial.write((time>>8) & 0xff);
|
||||||
|
Serial.write(temperature & 0xff);
|
||||||
|
Serial.write((temperature >> 8) & 0xff);
|
||||||
|
Serial.write(last_temperature & 0xff);
|
||||||
|
Serial.write((last_temperature >> 8) & 0xff);
|
||||||
|
Serial.write(profile_state & 0xff);
|
||||||
|
Serial.write((profile_state >> 8) & 0xff);
|
||||||
|
Serial.write(error_condition & 0xff);
|
||||||
|
Serial.write((error_condition >> 8) & 0xff);
|
||||||
|
Serial.write(is_oven_heating);
|
||||||
|
Serial.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OvenCtl::send_config() {
|
||||||
|
int tmp;
|
||||||
|
for (int i=0;i < PI_END; i++)
|
||||||
|
{
|
||||||
|
tmp = profile.data[i];
|
||||||
|
Serial.write(tmp & 0xff);
|
||||||
|
Serial.write((tmp >> 8 ) & 0xff);
|
||||||
|
}
|
||||||
|
Serial.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void OvenCtl::recv_config() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void OvenCtl::dispatch_input_config(int cmd) {
|
||||||
|
if (cmd == 255)
|
||||||
|
send_config();
|
||||||
|
else if (cmd == 254)
|
||||||
|
recv_config();
|
||||||
|
else if (cmd == 250)
|
||||||
|
reset();
|
||||||
|
else if (cmd == 253)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OvenCtl::handle_stand_alone_state() {
|
||||||
|
time++;
|
||||||
|
get_temp();
|
||||||
|
check_dt();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OvenCtl::handle_remote_state() {
|
||||||
|
time++;
|
||||||
|
get_temp();
|
||||||
|
check_dt();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* handles input, dispatching state dependend modes to state handlers
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void OvenCtl::loop() {
|
||||||
|
int cmd = -1;
|
||||||
|
|
||||||
|
switch (op_state) {
|
||||||
|
case OP_CONFIG:
|
||||||
|
// if (profile.handle_config_state())
|
||||||
|
// set_start_state();
|
||||||
|
break;
|
||||||
|
case OP_STAND_ALONE:
|
||||||
|
// if (profile.handle_config_state())
|
||||||
|
// set_start_state();
|
||||||
|
// break;
|
||||||
|
case OP_REMOTE:
|
||||||
|
// if (profile.handle_config_state())
|
||||||
|
// set_start_state();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (error_condition != 0) {
|
||||||
|
// set_error_state();
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (Serial.available() > 0) {
|
||||||
|
cmd = Serial.read();
|
||||||
|
if (cmd == 255)
|
||||||
|
send_config();
|
||||||
|
else if (cmd == 254)
|
||||||
|
send_state();
|
||||||
|
else if (cmd == 253)
|
||||||
|
recv_config();
|
||||||
|
else if (cmd == 252)
|
||||||
|
reset();
|
||||||
|
// else if (cmd == 251)
|
||||||
|
// set_start_state();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
control_oven();
|
||||||
|
if (profile_state > 0) {
|
||||||
|
print_status();
|
||||||
|
delay(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void OvenCtl::print_status() {
|
||||||
|
if (error_condition == 0) {
|
||||||
|
String tmp("T: ");
|
||||||
|
if (time < 10)
|
||||||
|
tmp += "00";
|
||||||
|
else if (time < 100)
|
||||||
|
tmp += "0";
|
||||||
|
tmp += time;
|
||||||
|
tmp += " Tmp: ";
|
||||||
|
if (temperature < 10)
|
||||||
|
tmp += "00";
|
||||||
|
else if (temperature < 100)
|
||||||
|
tmp += "0";
|
||||||
|
tmp += temperature;
|
||||||
|
lcd.setCursor(0, 0);
|
||||||
|
lcd.print(tmp);
|
||||||
|
|
||||||
|
tmp = "Profile: ";
|
||||||
|
tmp += profile_state;
|
||||||
|
tmp += "/";
|
||||||
|
tmp += END_STATE;
|
||||||
|
lcd.setCursor(0, 1);
|
||||||
|
lcd.print(tmp);
|
||||||
|
lcd.setCursor(13, 1);
|
||||||
|
if (is_oven_heating)
|
||||||
|
lcd.print("on ");
|
||||||
|
else
|
||||||
|
lcd.print("off");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
lcd.clear();
|
||||||
|
lcd.print("Error:");
|
||||||
|
lcd.setCursor(0, 1);
|
||||||
|
if (error_condition & E_DT_MIN)
|
||||||
|
lcd.print("K/s too low");
|
||||||
|
if (error_condition & E_DT_MAX)
|
||||||
|
lcd.print("K/s too high");
|
||||||
|
if (error_condition & E_TIME_MAX)
|
||||||
|
lcd.print("reflow too long");
|
||||||
|
if (error_condition & E_TS_TOO_SHORT)
|
||||||
|
lcd.print("ts too short");
|
||||||
|
if (error_condition & E_TS_TOO_LONG)
|
||||||
|
lcd.print("ts too long");
|
||||||
|
if (error_condition & E_TL_TOO_SHORT)
|
||||||
|
lcd.print("tal too short");
|
||||||
|
if (error_condition & E_TL_TOO_LONG)
|
||||||
|
lcd.print("tal too long");
|
||||||
|
if (error_condition & E_TP_TOO_LONG)
|
||||||
|
lcd.print("peak too short");
|
||||||
|
if (error_condition & E_TP_TOO_SHORT)
|
||||||
|
lcd.print("peak too long");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void OvenCtl::control_oven() {
|
||||||
|
if (temperature <= set_min + actual_hysteresis && !is_oven_heating) {
|
||||||
|
is_oven_heating = true;
|
||||||
|
// Serial.println("Oven turned on");
|
||||||
|
}
|
||||||
|
else if (temperature >= set_min + actual_hysteresis && is_oven_heating) {
|
||||||
|
is_oven_heating = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void OvenCtl::set_temp(int min, int max, int dt_min, int dt_max) {
|
||||||
|
set_min = min;
|
||||||
|
set_max = max;
|
||||||
|
set_dt_min = dt_min;
|
||||||
|
set_dt_max = dt_max;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void OvenCtl::get_temp() {
|
||||||
|
last_temperature = temperature;
|
||||||
|
temperature = int(float(analogRead(2)) * 0.2929);
|
||||||
|
actual_dt = float(temperature) - last_temperature;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OvenCtl::check_dt() {
|
||||||
|
if (disable_checks)
|
||||||
|
return;
|
||||||
|
if (actual_dt > set_dt_max) {
|
||||||
|
error_condition |= E_DT_MAX;
|
||||||
|
}
|
||||||
|
if (actual_dt < set_dt_min) {
|
||||||
|
error_condition |= E_DT_MIN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,139 @@
|
||||||
|
#ifndef _H_OVEN_CTL
|
||||||
|
#define _H_OVEN_CTL
|
||||||
|
|
||||||
|
/*
|
||||||
|
// operational states
|
||||||
|
#define OP_CONFIG 0
|
||||||
|
#define OP_STAND_ALONE 1
|
||||||
|
#define OP_REMOTE 2*/
|
||||||
|
|
||||||
|
enum OP_STATE {
|
||||||
|
OP_CONFIG,
|
||||||
|
OP_STAND_ALONE,
|
||||||
|
OP_REMOTE
|
||||||
|
};
|
||||||
|
|
||||||
|
#define E_DT_MIN 1 // temperature dt too small
|
||||||
|
#define E_DT_MAX 2 // temperature dt too big
|
||||||
|
#define E_TIME_MAX 4 // reflow process does take too long
|
||||||
|
#define E_TS_TOO_SHORT 8 // Ts duration too short
|
||||||
|
#define E_TS_TOO_LONG 16 // Ts duration too long
|
||||||
|
#define E_TL_TOO_SHORT 32 // Tl duration too short
|
||||||
|
#define E_TL_TOO_LONG 64 // Tl duration too long
|
||||||
|
#define E_TP_TOO_SHORT 128 // Tp duration too short
|
||||||
|
#define E_TP_TOO_LONG 256 // Tp duration too long
|
||||||
|
#define E_CONFIG 512 // error happened in config state
|
||||||
|
|
||||||
|
|
||||||
|
// profile states
|
||||||
|
enum PROFILE_STATE {
|
||||||
|
START_STATE,
|
||||||
|
PREHEAT_STATE,
|
||||||
|
RAMP_UP_STATE,
|
||||||
|
TAL_FIRST_STATE,
|
||||||
|
PEAK_STATE,
|
||||||
|
TAL_SECOND_STATE,
|
||||||
|
RAMP_DOWN_STATE,
|
||||||
|
END_STATE,
|
||||||
|
ERROR_STATE
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
class LiquidCrystal;
|
||||||
|
class DFR_Key;
|
||||||
|
class Profile;
|
||||||
|
|
||||||
|
extern Profile profile;
|
||||||
|
|
||||||
|
class OvenCtl {
|
||||||
|
public:
|
||||||
|
|
||||||
|
OvenCtl();
|
||||||
|
void loop();
|
||||||
|
// void set_config_state();
|
||||||
|
|
||||||
|
private:
|
||||||
|
// system time, timestamps and temperatures from sensors
|
||||||
|
int time; // profile seconds
|
||||||
|
int temperature; // actual oven temp
|
||||||
|
int last_temperature; // last oven temp
|
||||||
|
float actual_dt; // actual difference from last to actual temperatur
|
||||||
|
|
||||||
|
// timestamps of event beginnings/ends
|
||||||
|
int Ts_time_start;
|
||||||
|
int Ts_time_end;
|
||||||
|
int Tl_time_start;
|
||||||
|
int Tl_time_end;
|
||||||
|
int Tp_time_start;
|
||||||
|
int Tp_time_end;
|
||||||
|
|
||||||
|
// thermostat
|
||||||
|
float set_min;
|
||||||
|
float set_max;
|
||||||
|
int set_dt_min;
|
||||||
|
int set_dt_max;
|
||||||
|
|
||||||
|
float ramp_up_hysteresis; // duration in seconds
|
||||||
|
float ramp_down_hysteresis; // duration in seconds
|
||||||
|
float actual_hysteresis; // duration in seconds
|
||||||
|
|
||||||
|
// ui stuff
|
||||||
|
boolean disable_checks;
|
||||||
|
|
||||||
|
// state machine
|
||||||
|
unsigned int error_condition;
|
||||||
|
OP_STATE op_state;
|
||||||
|
PROFILE_STATE profile_state;
|
||||||
|
boolean is_oven_heating;
|
||||||
|
|
||||||
|
void print_status();
|
||||||
|
void control_oven();
|
||||||
|
void set_temp(int, int, int, int);
|
||||||
|
void get_temp();
|
||||||
|
void check_dt();
|
||||||
|
void check_max_duration();
|
||||||
|
|
||||||
|
// void set_start_state();
|
||||||
|
// void set_preheat_state();
|
||||||
|
// void set_tal_first_state();
|
||||||
|
// void set_ramp_up_state();
|
||||||
|
// void set_peak_state();
|
||||||
|
// void set_tal_second_state();
|
||||||
|
// void set_ramp_down_state();
|
||||||
|
// void set_end_state();
|
||||||
|
// void set_error_state();
|
||||||
|
|
||||||
|
// void handle_profile_states();
|
||||||
|
void handle_stand_alone_state();
|
||||||
|
void handle_remote_state();
|
||||||
|
|
||||||
|
// void handle_config_state();
|
||||||
|
// void handle_start_state();
|
||||||
|
// void handle_ramp_up_state();
|
||||||
|
// void handle_preheat_state();
|
||||||
|
// void handle_tal_first_state();
|
||||||
|
// void handle_peak_state();
|
||||||
|
// void handle_tal_second_state();
|
||||||
|
// void handle_ramp_down_state();
|
||||||
|
// void handle_end_state();
|
||||||
|
// void handle_error_state();
|
||||||
|
|
||||||
|
// void check_Ts_duration_min();
|
||||||
|
// void check_Ts_duration_max();
|
||||||
|
// void check_Tl_duration_min();
|
||||||
|
// void check_Tl_duration_max();
|
||||||
|
// void check_Tp_duration_min();
|
||||||
|
// void check_Tp_duration_max();
|
||||||
|
|
||||||
|
void send_state();
|
||||||
|
void send_config();
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
void recv_config();
|
||||||
|
|
||||||
|
void dispatch_input_config(int);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
477
plot.py
477
plot.py
|
@ -1,30 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
"""
|
|
||||||
GP:
|
|
||||||
Changed datasource, title, and refresh interval to use
|
|
||||||
as a poor man's Arduino oscilliscope.
|
|
||||||
|
|
||||||
This demo demonstrates how to draw a dynamic mpl (matplotlib)
|
|
||||||
plot in a wxPython application.
|
|
||||||
|
|
||||||
It allows "live" plotting as well as manual zooming to specific
|
|
||||||
regions.
|
|
||||||
|
|
||||||
Both X and Y axes allow "auto" or "manual" settings. For Y, auto
|
|
||||||
mode sets the scaling of the graph to see all the data points.
|
|
||||||
For X, auto mode makes the graph "follow" the data. Set it X min
|
|
||||||
to manual 0 to always see the whole data from the beginning.
|
|
||||||
|
|
||||||
Note: press Enter in the 'manual' text box to make a new value
|
|
||||||
affect the plot.
|
|
||||||
|
|
||||||
Eli Bendersky (eliben@gmail.com)
|
|
||||||
License: this code is in the public domain
|
|
||||||
Last modified: 31.07.2008
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import pprint
|
import pprint
|
||||||
import random
|
import random
|
||||||
|
@ -33,85 +8,40 @@ import wx
|
||||||
|
|
||||||
REFRESH_INTERVAL_MS = 1000
|
REFRESH_INTERVAL_MS = 1000
|
||||||
|
|
||||||
# The recommended way to use wx with mpl is with the WXAgg
|
|
||||||
# backend.
|
|
||||||
#
|
|
||||||
import matplotlib
|
import matplotlib
|
||||||
matplotlib.use('WXAgg')
|
matplotlib.use('WXAgg')
|
||||||
|
import matplotlib.lines
|
||||||
from matplotlib.figure import Figure
|
from matplotlib.figure import Figure
|
||||||
|
from matplotlib.pyplot import legend
|
||||||
from matplotlib.backends.backend_wxagg import \
|
from matplotlib.backends.backend_wxagg import \
|
||||||
FigureCanvasWxAgg as FigCanvas, \
|
FigureCanvasWxAgg as FigCanvas, \
|
||||||
NavigationToolbar2WxAgg as NavigationToolbar
|
NavigationToolbar2WxAgg as NavigationToolbar
|
||||||
from matplotlib.path import Path
|
from matplotlib.path import Path
|
||||||
import matplotlib.patches as patches
|
import matplotlib.patches as patches
|
||||||
|
import wx.lib.buttons as buttons
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pylab
|
import pylab
|
||||||
#Data comes from here
|
|
||||||
from Arduino_Monitor import SerialData as DataGen
|
from Arduino_Monitor import SerialData as DataGen
|
||||||
|
import Arduino_Monitor
|
||||||
|
|
||||||
class BoundControlBox(wx.Panel):
|
|
||||||
""" A static box with a couple of radio buttons and a text
|
|
||||||
box. Allows to switch between an automatic mode and a
|
|
||||||
manual mode with an associated value.
|
|
||||||
"""
|
|
||||||
def __init__(self, parent, ID, label, initval):
|
|
||||||
wx.Panel.__init__(self, parent, ID)
|
|
||||||
|
|
||||||
self.value = initval
|
|
||||||
|
|
||||||
box = wx.StaticBox(self, -1, label)
|
|
||||||
sizer = wx.StaticBoxSizer(box, wx.VERTICAL)
|
|
||||||
|
|
||||||
self.radio_auto = wx.RadioButton(self, -1,
|
|
||||||
label="Auto", style=wx.RB_GROUP)
|
|
||||||
self.radio_manual = wx.RadioButton(self, -1,
|
|
||||||
label="Manual")
|
|
||||||
self.manual_text = wx.TextCtrl(self, -1,
|
|
||||||
size=(35,-1),
|
|
||||||
value=str(initval),
|
|
||||||
style=wx.TE_PROCESS_ENTER)
|
|
||||||
|
|
||||||
self.Bind(wx.EVT_UPDATE_UI, self.on_update_manual_text, self.manual_text)
|
|
||||||
self.Bind(wx.EVT_TEXT_ENTER, self.on_text_enter, self.manual_text)
|
|
||||||
|
|
||||||
manual_box = wx.BoxSizer(wx.HORIZONTAL)
|
|
||||||
manual_box.Add(self.radio_manual, flag=wx.ALIGN_CENTER_VERTICAL)
|
|
||||||
manual_box.Add(self.manual_text, flag=wx.ALIGN_CENTER_VERTICAL)
|
|
||||||
|
|
||||||
sizer.Add(self.radio_auto, 0, wx.ALL, 10)
|
|
||||||
sizer.Add(manual_box, 0, wx.ALL, 10)
|
|
||||||
self.radio_auto.SetValue(False);
|
|
||||||
self.radio_manual.SetValue(True);
|
|
||||||
|
|
||||||
self.SetSizer(sizer)
|
|
||||||
sizer.Fit(self)
|
|
||||||
|
|
||||||
def on_update_manual_text(self, event):
|
|
||||||
self.manual_text.Enable(self.radio_manual.GetValue())
|
|
||||||
|
|
||||||
def on_text_enter(self, event):
|
|
||||||
self.value = self.manual_text.GetValue()
|
|
||||||
|
|
||||||
def is_auto(self):
|
|
||||||
return self.radio_auto.GetValue()
|
|
||||||
|
|
||||||
def manual_value(self):
|
|
||||||
return self.value
|
|
||||||
|
|
||||||
|
|
||||||
class GraphFrame(wx.Frame):
|
class GraphFrame(wx.Frame):
|
||||||
""" The main frame of the application
|
""" The main frame of the application
|
||||||
"""
|
"""
|
||||||
title = 'Demo: dynamic matplotlib graph'
|
title = 'reflowctl gui'
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
wx.Frame.__init__(self, None, -1, self.title)
|
wx.Frame.__init__(self, None, -1, self.title)
|
||||||
|
|
||||||
self.datagen = DataGen()
|
self.datagen = DataGen()
|
||||||
self.data = [self.datagen.next()]
|
self.data = [self.datagen.next()]
|
||||||
self.paused = False
|
self.started = False
|
||||||
|
|
||||||
|
self.profile = []
|
||||||
|
self.state = []
|
||||||
|
self.count = 0
|
||||||
|
|
||||||
self.create_menu()
|
self.create_menu()
|
||||||
self.create_status_bar()
|
self.create_status_bar()
|
||||||
|
@ -121,6 +51,7 @@ class GraphFrame(wx.Frame):
|
||||||
self.Bind(wx.EVT_TIMER, self.on_redraw_timer, self.redraw_timer)
|
self.Bind(wx.EVT_TIMER, self.on_redraw_timer, self.redraw_timer)
|
||||||
self.redraw_timer.Start(REFRESH_INTERVAL_MS)
|
self.redraw_timer.Start(REFRESH_INTERVAL_MS)
|
||||||
|
|
||||||
|
|
||||||
def create_menu(self):
|
def create_menu(self):
|
||||||
self.menubar = wx.MenuBar()
|
self.menubar = wx.MenuBar()
|
||||||
|
|
||||||
|
@ -137,110 +68,274 @@ class GraphFrame(wx.Frame):
|
||||||
def create_main_panel(self):
|
def create_main_panel(self):
|
||||||
self.panel = wx.Panel(self)
|
self.panel = wx.Panel(self)
|
||||||
|
|
||||||
|
self.hbox1 = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
self.init_profile()
|
||||||
|
self.init_log()
|
||||||
|
self.init_oven_status()
|
||||||
self.init_plot()
|
self.init_plot()
|
||||||
self.canvas = FigCanvas(self.panel, -1, self.fig)
|
self.canvas = FigCanvas(self.panel, -1, self.fig)
|
||||||
|
|
||||||
self.xmin_control = BoundControlBox(self.panel, -1, "X min", 0)
|
self.recv_config_button = wx.Button(self.panel, -1, "Receive Config")
|
||||||
self.xmax_control = BoundControlBox(self.panel, -1, "X max", 250)
|
self.Bind(wx.EVT_BUTTON, self.on_recv_config_button, self.recv_config_button)
|
||||||
self.ymin_control = BoundControlBox(self.panel, -1, "Y min", 0)
|
|
||||||
self.ymax_control = BoundControlBox(self.panel, -1, "Y max", 280)
|
|
||||||
|
|
||||||
self.pause_button = wx.Button(self.panel, -1, "Pause")
|
self.send_button = wx.Button(self.panel, -1, "Send Config")
|
||||||
self.Bind(wx.EVT_BUTTON, self.on_pause_button, self.pause_button)
|
self.Bind(wx.EVT_BUTTON, self.on_send_button, self.send_button)
|
||||||
self.Bind(wx.EVT_UPDATE_UI, self.on_update_pause_button, self.pause_button)
|
|
||||||
|
|
||||||
self.cb_grid = wx.CheckBox(self.panel, -1,
|
self.start_button = buttons.GenToggleButton(self.panel, -1, "Start")
|
||||||
"Show Grid",
|
self.Bind(wx.EVT_BUTTON, self.on_start_button, self.start_button)
|
||||||
style=wx.ALIGN_RIGHT)
|
|
||||||
self.Bind(wx.EVT_CHECKBOX, self.on_cb_grid, self.cb_grid)
|
|
||||||
self.cb_grid.SetValue(True)
|
|
||||||
|
|
||||||
self.cb_xlab = wx.CheckBox(self.panel, -1,
|
#self.on_bitmap = wx.Image('burn.png', wx.BITMAP_TYPE_PNG).Scale(32, 32, wx.IMAGE_QUALITY_HIGH).ConvertToBitmap()
|
||||||
"Show X labels",
|
#self.off_bitmap = wx.Image('unburn.png', wx.BITMAP_TYPE_PNG).ConvertToBitmap()
|
||||||
style=wx.ALIGN_RIGHT)
|
|
||||||
self.Bind(wx.EVT_CHECKBOX, self.on_cb_xlab, self.cb_xlab)
|
|
||||||
self.cb_xlab.SetValue(True)
|
|
||||||
|
|
||||||
self.hbox1 = wx.BoxSizer(wx.HORIZONTAL)
|
|
||||||
self.hbox1.Add(self.pause_button, border=5, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL)
|
|
||||||
self.hbox1.AddSpacer(5)
|
|
||||||
self.hbox1.Add(self.cb_grid, border=5, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL)
|
|
||||||
self.hbox1.AddSpacer(5)
|
|
||||||
self.hbox1.Add(self.cb_xlab, border=5, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL)
|
|
||||||
|
|
||||||
self.hbox2 = wx.BoxSizer(wx.HORIZONTAL)
|
self.ctrls = wx.BoxSizer(wx.VERTICAL)
|
||||||
self.hbox2.Add(self.xmin_control, border=5, flag=wx.ALL)
|
self.ctrls.Add(self.recv_config_button, border=5, flag=wx.ALL | wx.ALIGN_LEFT)
|
||||||
self.hbox2.Add(self.xmax_control, border=5, flag=wx.ALL)
|
self.ctrls.Add(self.send_button, border=5, flag=wx.ALL | wx.ALIGN_LEFT)
|
||||||
self.hbox2.AddSpacer(24)
|
self.ctrls.Add(self.start_button, border=5, flag=wx.ALL | wx.ALIGN_LEFT)
|
||||||
self.hbox2.Add(self.ymin_control, border=5, flag=wx.ALL)
|
self.hbox1.Add(self.ctrls, border=5, flag=wx.ALL | wx.ALIGN_TOP)
|
||||||
self.hbox2.Add(self.ymax_control, border=5, flag=wx.ALL)
|
|
||||||
|
|
||||||
self.vbox = wx.BoxSizer(wx.VERTICAL)
|
self.vbox = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
self.vbox.Add(self.hbox1, 0, flag=wx.ALIGN_LEFT | wx.ALL)
|
||||||
self.vbox.Add(self.canvas, 1, flag=wx.LEFT | wx.TOP | wx.GROW)
|
self.vbox.Add(self.canvas, 1, flag=wx.LEFT | wx.TOP | wx.GROW)
|
||||||
self.vbox.Add(self.hbox1, 0, flag=wx.ALIGN_LEFT | wx.TOP)
|
#self.vbox.Add(self.hbox1, 0, flag=wx.ALIGN_LEFT | wx.TOP)
|
||||||
self.vbox.Add(self.hbox2, 0, flag=wx.ALIGN_LEFT | wx.TOP)
|
|
||||||
|
|
||||||
self.panel.SetSizer(self.vbox)
|
self.panel.SetSizer(self.vbox)
|
||||||
self.vbox.Fit(self)
|
self.vbox.Fit(self)
|
||||||
|
|
||||||
|
def profile_spin_changed(self, event):
|
||||||
|
print dir(event)
|
||||||
|
|
||||||
|
|
||||||
|
def add_profile_item(self, title, sizer, min_=1, max_=250):
|
||||||
|
|
||||||
|
mc = 8
|
||||||
|
|
||||||
|
item = wx.SpinCtrl(self.panel, -1, "", (30, 50))
|
||||||
|
item.SetRange(min_, max_)
|
||||||
|
item.SetValue(Arduino_Monitor.profile[self.count])
|
||||||
|
|
||||||
|
self.Bind(wx.EVT_SPIN, self.profile_spin_changed, item)
|
||||||
|
|
||||||
|
sizer.Add(wx.StaticText(self.panel, -1, title), (self.count, 0))
|
||||||
|
sizer.Add(item, (self.count, 1))
|
||||||
|
|
||||||
|
self.count += 1
|
||||||
|
self.profile.append(item)
|
||||||
|
|
||||||
|
def init_profile(self):
|
||||||
|
self.preheat_sizer = wx.GridBagSizer(5, 5)
|
||||||
|
self.rampup_sizer = wx.GridBagSizer(5, 5)
|
||||||
|
self.peak_sizer = wx.GridBagSizer(5, 5)
|
||||||
|
self.rampdown_sizer = wx.GridBagSizer(5, 5)
|
||||||
|
|
||||||
|
self.add_profile_item("Ts_min (°C)", self.preheat_sizer, 0, 300)
|
||||||
|
self.add_profile_item("Ts_max (°C)", self.preheat_sizer, 0, 300)
|
||||||
|
self.add_profile_item("Ts duration min (s)", self.preheat_sizer, 0, 300)
|
||||||
|
self.add_profile_item("Ts duration max (s)", self.preheat_sizer, 0, 300)
|
||||||
|
|
||||||
|
self.add_profile_item("ts ramp up min (°C/s)", self.preheat_sizer, 1, 100)
|
||||||
|
self.add_profile_item("ts ramp up max (°C/s)", self.preheat_sizer, 1, 100)
|
||||||
|
self.add_profile_item("tp ramp up min (°C/s)", self.peak_sizer, 1, 100)
|
||||||
|
self.add_profile_item("tp ramp up max (°C/s)", self.peak_sizer1, 100)
|
||||||
|
|
||||||
|
self.add_profile_item("Tl duration min (s)", self.rampup_sizer, 0, 300)
|
||||||
|
self.add_profile_item("Tl duration max (s)", self.rampup_sizer, 0, 300)
|
||||||
|
|
||||||
|
self.add_profile_item("Tp (°C)", self.peak_sizer, 0, 300)
|
||||||
|
self.add_profile_item("Tp duration min (s)", self.peak_sizer, 0, 300)
|
||||||
|
self.add_profile_item("Tp duration max (s)", self.peak_sizer, 0, 300)
|
||||||
|
|
||||||
|
self.add_profile_item("ramp down min (°C/s)", self.rampdown_sizer, -100, 0)
|
||||||
|
self.add_profile_item("ramp down max (°C/s)", self.rampdown_sizer, -100, 0)
|
||||||
|
|
||||||
|
self.add_profile_item("time max (s)", 0, 800)
|
||||||
|
|
||||||
|
self.box = wx.StaticBox(self.panel, -1, "Profile Settings")
|
||||||
|
self.bsizer = wx.StaticBoxSizer(self.box, wx.VERTICAL)
|
||||||
|
self.bsizer.Add(self.profile_sizer, 0, flag=wx.ALL, border=5)
|
||||||
|
self.hbox1.Add(self.bsizer, border=5, flag=wx.ALL | wx.ALIGN_TOP)
|
||||||
|
|
||||||
|
def init_oven_status(self):
|
||||||
|
self.oven_status_sizer = wx.GridBagSizer(5, 5)
|
||||||
|
|
||||||
|
#set_min = 0;
|
||||||
|
#set_max = 0;
|
||||||
|
#set_dt_min = 0;
|
||||||
|
#set_dt_max = 0;
|
||||||
|
|
||||||
|
self.oven_status_sizer.Add(wx.StaticText(self.panel, -1, "Connected"), (0, 0))
|
||||||
|
self.oven_connected = wx.StaticText(self.panel, -1, str(self.datagen.connected()))
|
||||||
|
self.oven_status_sizer.Add(self.oven_connected, (0, 1))
|
||||||
|
|
||||||
|
self.oven_status_sizer.Add(wx.StaticText(self.panel, -1, "Temperature"), (1, 0))
|
||||||
|
self.temperature = wx.TextCtrl(self.panel, -1, str(Arduino_Monitor.status[1]))
|
||||||
|
self.oven_status_sizer.Add(self.temperature, (1, 1))
|
||||||
|
|
||||||
|
self.oven_status_sizer.Add(wx.StaticText(self.panel, -1, "Time"), (2, 0))
|
||||||
|
self.time = wx.TextCtrl(self.panel, -1, str(Arduino_Monitor.status[0]))
|
||||||
|
self.oven_status_sizer.Add(self.time, (2, 1))
|
||||||
|
|
||||||
|
|
||||||
|
self.oven_status_sizer.Add(wx.StaticText(self.panel, -1, "State"), (3, 0))
|
||||||
|
self.pstate = wx.TextCtrl(self.panel, -1, str(Arduino_Monitor.status[3]))
|
||||||
|
self.oven_status_sizer.Add(self.pstate, (3, 1))
|
||||||
|
|
||||||
|
self.oven_status_sizer.Add(wx.StaticText(self.panel, -1, "Error"), (4, 0))
|
||||||
|
self.perror = wx.TextCtrl(self.panel, -1, str(Arduino_Monitor.status[4]))
|
||||||
|
self.oven_status_sizer.Add(self.perror, (4, 1))
|
||||||
|
|
||||||
|
self.oven_status_sizer.Add(wx.StaticText(self.panel, -1, "Heating"), (5, 0))
|
||||||
|
self.is_oven_heating = wx.TextCtrl(self.panel, -1, str(Arduino_Monitor.status[5]))
|
||||||
|
self.oven_status_sizer.Add(self.is_oven_heating, (5, 1))
|
||||||
|
|
||||||
|
self.obox = wx.StaticBox(self.panel, -1, "Oven status")
|
||||||
|
self.osizer = wx.StaticBoxSizer(self.obox, wx.VERTICAL)
|
||||||
|
self.osizer.Add(self.oven_status_sizer, 0, flag=wx.ALL, border=5)
|
||||||
|
self.hbox1.Add(self.osizer, border=5, flag=wx.ALL | wx.ALIGN_TOP)
|
||||||
|
|
||||||
|
|
||||||
|
def init_log(self):
|
||||||
|
self.log_sizer = wx.GridBagSizer(5, 5)
|
||||||
|
|
||||||
|
self.log_sizer.Add(wx.StaticText(self.panel, -1, "Ts_time_start"), (0, 0))
|
||||||
|
self.ts_time_start = wx.TextCtrl(self.panel, -1)
|
||||||
|
self.log_sizer.Add(self.ts_time_start, (0, 1))
|
||||||
|
|
||||||
|
self.log_sizer.Add(wx.StaticText(self.panel, -1, "Ts_time_end"), (1, 0))
|
||||||
|
self.ts_time_end = wx.TextCtrl(self.panel, -1)
|
||||||
|
self.log_sizer.Add(self.ts_time_end, (1, 1))
|
||||||
|
|
||||||
|
self.log_sizer.Add(wx.StaticText(self.panel, -1, "Tl_time_start"), (2, 0))
|
||||||
|
self.tl_time_start = wx.TextCtrl(self.panel, -1)
|
||||||
|
self.log_sizer.Add(self.tl_time_start, (2, 1))
|
||||||
|
|
||||||
|
self.log_sizer.Add(wx.StaticText(self.panel, -1, "Tl_time_end"), (3, 0))
|
||||||
|
self.tl_time_end = wx.TextCtrl(self.panel, -1)
|
||||||
|
self.log_sizer.Add(self.tl_time_end, (3, 1))
|
||||||
|
|
||||||
|
|
||||||
|
self.log_sizer.Add(wx.StaticText(self.panel, -1, "Tp_time_start"), (4, 0))
|
||||||
|
self.tp_time_start = wx.TextCtrl(self.panel, -1)
|
||||||
|
self.log_sizer.Add(self.tp_time_start, (4, 1))
|
||||||
|
|
||||||
|
self.log_sizer.Add(wx.StaticText(self.panel, -1, "Tp_time_end"), (5, 0))
|
||||||
|
self.tp_time_end = wx.TextCtrl(self.panel, -1)
|
||||||
|
self.log_sizer.Add(self.tp_time_end, (5, 1))
|
||||||
|
|
||||||
|
self.lbox = wx.StaticBox(self.panel, -1, "Profile Log")
|
||||||
|
self.lsizer = wx.StaticBoxSizer(self.lbox, wx.VERTICAL)
|
||||||
|
self.lsizer.Add(self.log_sizer, 0, flag=wx.ALL, border=5)
|
||||||
|
self.hbox1.Add(self.lsizer, border=5, flag=wx.ALL | wx.ALIGN_TOP)
|
||||||
|
|
||||||
def create_status_bar(self):
|
def create_status_bar(self):
|
||||||
self.statusbar = self.CreateStatusBar()
|
self.statusbar = self.CreateStatusBar()
|
||||||
|
|
||||||
def init_plot(self):
|
def init_plot(self):
|
||||||
self.dpi = 100
|
self.dpi = 100
|
||||||
self.fig = Figure((3.0, 3.0), dpi=self.dpi)
|
self.fig = Figure((4.0, 4.0), dpi=self.dpi)
|
||||||
|
|
||||||
self.axes = self.fig.add_subplot(111)
|
self.axes = self.fig.add_subplot(111)
|
||||||
self.axes.set_axis_bgcolor('black')
|
self.axes.set_axis_bgcolor('black')
|
||||||
self.axes.set_title(u'Reflow Temperature', size=12)
|
self.axes.set_title(u'Reflow Temperature', size=12)
|
||||||
self.axes.set_xlabel(u'Time / seconds', size=12)
|
self.axes.set_xlabel(u'Time / seconds', size=12)
|
||||||
self.axes.set_ylabel(u'Temperature / °C', size=12)
|
self.axes.set_ylabel(u'Temperature (°C)', size=12)
|
||||||
|
|
||||||
pylab.setp(self.axes.get_xticklabels(), fontsize=8)
|
pylab.setp(self.axes.get_xticklabels(), fontsize=8)
|
||||||
pylab.setp(self.axes.get_yticklabels(), fontsize=8)
|
pylab.setp(self.axes.get_yticklabels(), fontsize=8)
|
||||||
|
|
||||||
# plot the data as a line series, and save the reference
|
# no 1
|
||||||
# to the plotted line series
|
|
||||||
#
|
|
||||||
|
|
||||||
ts_min_x_min = Arduino_Monitor.profile[Arduino_Monitor.PI_TS_MIN] / Arduino_Monitor.profile[Arduino_Monitor.PI_RAMP_UP_MAX]
|
ts_min_x_min = Arduino_Monitor.profile[Arduino_Monitor.PI_TS_MIN] / Arduino_Monitor.profile[Arduino_Monitor.PI_RAMP_UP_MAX]
|
||||||
ts_min_y_min = ts_min
|
ts_min_y_min = Arduino_Monitor.profile[Arduino_Monitor.PI_TS_MIN]
|
||||||
|
|
||||||
|
# no 2
|
||||||
ts_max_x_min = ts_min_x_min + Arduino_Monitor.profile[Arduino_Monitor.PI_TS_DURATION_MIN]
|
ts_max_x_min = ts_min_x_min + Arduino_Monitor.profile[Arduino_Monitor.PI_TS_DURATION_MIN]
|
||||||
ts_max_y_min = ts_max
|
ts_max_y_min = Arduino_Monitor.profile[Arduino_Monitor.PI_TS_MAX]
|
||||||
|
|
||||||
|
# no t1
|
||||||
ts_max_x_max = ts_min_x_min + Arduino_Monitor.profile[Arduino_Monitor.PI_TS_DURATION_MAX]
|
ts_max_x_max = ts_min_x_min + Arduino_Monitor.profile[Arduino_Monitor.PI_TS_DURATION_MAX]
|
||||||
ts_max_y_max = ts_max
|
ts_max_y_max = Arduino_Monitor.profile[Arduino_Monitor.PI_TS_MAX]
|
||||||
|
|
||||||
ts_min_x_max = ts_max_x_max - (ts_max_y_max - ts_min_y) / Arduino_Monitor.profile[Arduino_Monitor.PI_RAMP_UP_MAX])
|
# no t2
|
||||||
ts_min_y_max = ts_min
|
ts_min_x_max = ts_max_x_max - (ts_max_y_max - ts_min_y_min) / Arduino_Monitor.profile[Arduino_Monitor.PI_RAMP_UP_MAX]
|
||||||
|
ts_min_y_max = Arduino_Monitor.profile[Arduino_Monitor.PI_TS_MIN]
|
||||||
|
|
||||||
tl_x_min = ts_max_x_min + (Arduino_Monitor.profile[Arduino_Monitor.PI_TL] - Arduino_Monitor.profile[Arduino_Monitor.PI_TS_MAX]) / Arduino_Monitor.profile[Arduino_Monitor.PI_RAMP_UP_MAX]
|
# no 10
|
||||||
tl_x_min = Arduino_Monitor.profile[Arduino_Monitor.PI_TL]
|
t0_x_max = ts_min_x_max - (ts_max_x_max / Arduino_Monitor.profile[Arduino_Monitor.PI_RAMP_UP_MAX])
|
||||||
|
t0_y_max = 0
|
||||||
|
|
||||||
tl_x_min = ts_max_x_min + (Arduino_Monitor.profile[Arduino_Monitor.PI_TL] - Arduino_Monitor.profile[Arduino_Monitor.PI_TS_MAX]) / Arduino_Monitor.profile[Arduino_Monitor.PI_RAMP_UP_MAX]
|
# no 4
|
||||||
tl_y_min = Arduino_Monitor.profile[Arduino_Monitor.PI_TL]
|
tp_x_min = ts_max_x_min + (Arduino_Monitor.profile[Arduino_Monitor.PI_TP] - ts_max_y_min) / Arduino_Monitor.profile[Arduino_Monitor.PI_RAMP_UP_MAX]
|
||||||
|
tp_y_min = Arduino_Monitor.profile[Arduino_Monitor.PI_TP]
|
||||||
|
|
||||||
tl_x_max = tl_x_min + Arduino_Monitor.profile[Arduino_Monitor.PI_TL_DURATION_MIN]
|
# no 5
|
||||||
tl_y_max = Arduino_Monitor.profile[Arduino_Monitor.PI_TL]
|
tp_x_max = tp_x_min + Arduino_Monitor.profile[Arduino_Monitor.PI_TP_DURATION_MAX]
|
||||||
|
tp_y_max = tp_y_min
|
||||||
|
|
||||||
|
# no 8
|
||||||
|
tp5_x_max = tp_x_min + Arduino_Monitor.profile[Arduino_Monitor.PI_TP_DURATION_MIN]
|
||||||
|
tp5_y_max = tp_y_max - 5
|
||||||
|
|
||||||
|
# no 9
|
||||||
|
tp5_x_min = ts_max_x_max + (tp5_y_max - ts_max_y_max) / Arduino_Monitor.profile[Arduino_Monitor.PI_RAMP_UP_MAX]
|
||||||
|
tp5_y_min = tp5_y_max
|
||||||
|
|
||||||
|
# no 6
|
||||||
|
end_x_max = tp_x_max + tp_y_max / abs(Arduino_Monitor.profile[Arduino_Monitor.PI_RAMP_DOWN_MIN])
|
||||||
|
end_y_max = 0
|
||||||
|
|
||||||
|
self.xmax = end_x_max + 20
|
||||||
|
self.ymax = Arduino_Monitor.profile[Arduino_Monitor.PI_TP] + 20
|
||||||
|
|
||||||
|
# no 7
|
||||||
|
end_x_min = tp5_x_max + tp5_y_max / abs(Arduino_Monitor.profile[Arduino_Monitor.PI_RAMP_DOWN_MAX])
|
||||||
|
end_y_min = 0
|
||||||
|
|
||||||
|
tsmin = Arduino_Monitor.profile[Arduino_Monitor.PI_TS_MIN]
|
||||||
|
tsmax = Arduino_Monitor.profile[Arduino_Monitor.PI_TS_MAX]
|
||||||
|
tl = Arduino_Monitor.profile[Arduino_Monitor.PI_TL]
|
||||||
|
tp = Arduino_Monitor.profile[Arduino_Monitor.PI_TP]
|
||||||
|
self.ts_line_min = matplotlib.lines.Line2D([0, self.xmax], [tsmin, tsmin],
|
||||||
|
transform=self.axes.transData, figure=self.fig, color='green')
|
||||||
|
self.ts_line_max = matplotlib.lines.Line2D([0, self.xmax], [tsmax, tsmax],
|
||||||
|
transform=self.axes.transData, figure=self.fig, label="Ts_max", color='lightgreen')
|
||||||
|
self.tl_line = matplotlib.lines.Line2D([0, self.xmax], [tl, tl],
|
||||||
|
transform=self.axes.transData, figure=self.fig, label="Tl", color='yellow')
|
||||||
|
self.tp_line = matplotlib.lines.Line2D([0, self.xmax], [tp, tp],
|
||||||
|
transform=self.axes.transData, figure=self.fig, label="Tp", color='blue')
|
||||||
|
|
||||||
|
self.ts_line_min.set_label("Ts_min")
|
||||||
|
self.ts_line_min.set_label("Ts_max")
|
||||||
|
self.tl_line.set_label("Tl")
|
||||||
|
self.tp_line.set_label("Tp")
|
||||||
|
self.fig.lines.extend([self.ts_line_min, self.ts_line_max, self.tl_line, self.tp_line])
|
||||||
|
|
||||||
verts = [
|
verts = [
|
||||||
[ 0.0, 0.0],
|
[0.0, 0.0],
|
||||||
[ 75.0, 150.0],
|
[ts_min_x_min, ts_min_y_min],
|
||||||
[100.0, 200.0],
|
[ts_max_x_min, ts_max_y_min],
|
||||||
[108.5, 217.0],
|
[ts_max_x_max, ts_max_y_max],
|
||||||
[130.0, 260.0],
|
[ts_min_x_max, ts_min_y_max],
|
||||||
[170.0, 260.0],
|
#[tp_x_min, tp_y_min],
|
||||||
[300.0, 0.0],
|
#[tp_x_max, tp_y_max],
|
||||||
[ 0.0, 0.0]]
|
#[end_x_max, end_y_max],
|
||||||
|
#[end_x_min, end_y_min],
|
||||||
|
#[tp5_x_max, tp5_y_max],
|
||||||
|
#[tp5_x_min, tp5_y_min],
|
||||||
|
[t0_x_max, t0_y_max],
|
||||||
|
[0.0, 0.0]]
|
||||||
|
|
||||||
codes = [Path.MOVETO,
|
codes = [
|
||||||
Path.LINETO,
|
Path.MOVETO,
|
||||||
Path.LINETO,
|
Path.LINETO,
|
||||||
Path.LINETO,
|
Path.LINETO,
|
||||||
Path.LINETO,
|
Path.LINETO,
|
||||||
Path.LINETO,
|
Path.LINETO,
|
||||||
Path.LINETO,
|
Path.LINETO,
|
||||||
|
#Path.LINETO,
|
||||||
|
#Path.LINETO,
|
||||||
|
#Path.LINETO,
|
||||||
|
#Path.LINETO,
|
||||||
Path.CLOSEPOLY]
|
Path.CLOSEPOLY]
|
||||||
|
|
||||||
self.plot_data = self.axes.plot(
|
self.plot_data = self.axes.plot(
|
||||||
|
@ -249,86 +344,62 @@ class GraphFrame(wx.Frame):
|
||||||
color=(1, 1, 0),
|
color=(1, 1, 0),
|
||||||
)[0]
|
)[0]
|
||||||
|
|
||||||
|
print "verts", verts
|
||||||
|
|
||||||
|
|
||||||
path = Path(verts, codes)
|
path = Path(verts, codes)
|
||||||
self.patch = patches.PathPatch(path, edgecolor="red", facecolor='orange', lw=2)
|
self.patch = patches.PathPatch(path, edgecolor="red", facecolor='orange', lw=2)
|
||||||
self.axes.add_patch(self.patch)
|
self.axes.add_patch(self.patch)
|
||||||
|
self.axes.legend( (self.ts_line_min, self.ts_line_max, self.tl_line, self.tp_line), ('Ts_min', 'Ts_max', 'Tl', 'Tp'), loc=2)
|
||||||
|
|
||||||
def draw_plot(self):
|
def draw_plot(self):
|
||||||
""" Redraws the plot
|
""" Redraws the plot
|
||||||
"""
|
"""
|
||||||
# when xmin is on auto, it "follows" xmax to produce a
|
|
||||||
# sliding window effect. therefore, xmin is assigned after
|
|
||||||
# xmax.
|
|
||||||
#
|
|
||||||
if self.xmax_control.is_auto():
|
|
||||||
xmax = len(self.data) if len(self.data) > 50 else 50
|
|
||||||
else:
|
|
||||||
xmax = int(self.xmax_control.manual_value())
|
|
||||||
|
|
||||||
if self.xmin_control.is_auto():
|
self.axes.set_xbound(lower=0, upper=self.xmax)
|
||||||
xmin = xmax - 50
|
self.axes.set_ybound(lower=0, upper=self.ymax)
|
||||||
else:
|
|
||||||
xmin = int(self.xmin_control.manual_value())
|
|
||||||
|
|
||||||
#xmax = 480
|
self.axes.grid(True, color='gray')
|
||||||
|
|
||||||
# for ymin and ymax, find the minimal and maximal values
|
pylab.setp(self.axes.get_xticklabels(), visible=True)
|
||||||
# in the data set and add a mininal margin.
|
|
||||||
#
|
|
||||||
# note that it's easy to change this scheme to the
|
|
||||||
# minimal/maximal value in the current display, and not
|
|
||||||
# the whole data set.
|
|
||||||
#
|
|
||||||
if self.ymin_control.is_auto():
|
|
||||||
ymin = round(min(self.data), 0) - 1
|
|
||||||
else:
|
|
||||||
ymin = int(self.ymin_control.manual_value())
|
|
||||||
|
|
||||||
if self.ymax_control.is_auto():
|
|
||||||
ymax = round(max(self.data), 0) + 1
|
|
||||||
else:
|
|
||||||
ymax = int(self.ymax_control.manual_value())
|
|
||||||
|
|
||||||
#ymax = 300
|
|
||||||
|
|
||||||
self.axes.set_xbound(lower=xmin, upper=xmax)
|
|
||||||
self.axes.set_ybound(lower=ymin, upper=ymax)
|
|
||||||
|
|
||||||
# anecdote: axes.grid assumes b=True if any other flag is
|
|
||||||
# given even if b is set to False.
|
|
||||||
# so just passing the flag into the first statement won't
|
|
||||||
# work.
|
|
||||||
#
|
|
||||||
if self.cb_grid.IsChecked():
|
|
||||||
self.axes.grid(True, color='gray')
|
|
||||||
else:
|
|
||||||
self.axes.grid(False)
|
|
||||||
|
|
||||||
# Using setp here is convenient, because get_xticklabels
|
|
||||||
# returns a list over which one needs to explicitly
|
|
||||||
# iterate, and setp already handles this.
|
|
||||||
#
|
|
||||||
pylab.setp(self.axes.get_xticklabels(),
|
|
||||||
visible=self.cb_xlab.IsChecked())
|
|
||||||
|
|
||||||
self.plot_data.set_xdata(np.arange(len(self.data)))
|
self.plot_data.set_xdata(np.arange(len(self.data)))
|
||||||
self.plot_data.set_ydata(np.array(self.data))
|
self.plot_data.set_ydata(np.array(self.data))
|
||||||
|
|
||||||
self.canvas.draw()
|
self.canvas.draw()
|
||||||
|
|
||||||
def on_pause_button(self, event):
|
def update_config(self):
|
||||||
self.paused = not self.paused
|
for ix, i in enumerate(self.profile):
|
||||||
|
i.SetValue(str(Arduino_Monitor.profile[i]))
|
||||||
|
|
||||||
def on_update_pause_button(self, event):
|
def update_state(self):
|
||||||
label = "Resume" if self.paused else "Pause"
|
if Arduino_Monitor.status[3] > 0:
|
||||||
self.pause_button.SetLabel(label)
|
self.started = True
|
||||||
|
|
||||||
def on_cb_grid(self, event):
|
self.time.SetValue(str(Arduino_Monitor.status[0]))
|
||||||
self.draw_plot()
|
self.temperature.SetValue(str(Arduino_Monitor.status[1]))
|
||||||
|
self.pstate.SetValue(str(Arduino_Monitor.status[3]))
|
||||||
|
self.perror.SetValue(str(Arduino_Monitor.status[4]))
|
||||||
|
self.is_oven_heating.SetValue(str(Arduino_Monitor.status[5]))
|
||||||
|
|
||||||
def on_cb_xlab(self, event):
|
|
||||||
self.draw_plot()
|
def on_start_button(self, event):
|
||||||
|
self.started = self.datagen.send_start()
|
||||||
|
self.recv_config_button.Disable()
|
||||||
|
self.send_button.Disable()
|
||||||
|
self.profile = []
|
||||||
|
for i in range(30):
|
||||||
|
self.profile_sizer.Remove(i)
|
||||||
|
self.profile_sizer.Layout()
|
||||||
|
|
||||||
|
def on_recv_config_button(self, event):
|
||||||
|
if not self.started:
|
||||||
|
self.datagen.recv_config()
|
||||||
|
|
||||||
|
|
||||||
|
def on_send_button(self, event):
|
||||||
|
if not self.started:
|
||||||
|
self.datagen.send_config()
|
||||||
|
|
||||||
def on_save_plot(self, event):
|
def on_save_plot(self, event):
|
||||||
file_choices = "PNG (*.png)|*.png"
|
file_choices = "PNG (*.png)|*.png"
|
||||||
|
@ -347,12 +418,11 @@ class GraphFrame(wx.Frame):
|
||||||
self.flash_status_message("Saved to %s" % path)
|
self.flash_status_message("Saved to %s" % path)
|
||||||
|
|
||||||
def on_redraw_timer(self, event):
|
def on_redraw_timer(self, event):
|
||||||
# if paused do not add data, but still redraw the plot
|
|
||||||
# (to respond to scale modifications, grid change, etc.)
|
if self.started:
|
||||||
#
|
|
||||||
if not self.paused:
|
|
||||||
self.data.append(self.datagen.next())
|
self.data.append(self.datagen.next())
|
||||||
|
|
||||||
|
self.update_state()
|
||||||
self.draw_plot()
|
self.draw_plot()
|
||||||
|
|
||||||
def on_exit(self, event):
|
def on_exit(self, event):
|
||||||
|
@ -376,3 +446,4 @@ if __name__ == '__main__':
|
||||||
app.frame = GraphFrame()
|
app.frame = GraphFrame()
|
||||||
app.frame.Show()
|
app.frame.Show()
|
||||||
app.MainLoop()
|
app.MainLoop()
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,219 @@
|
||||||
|
#include "profile.h"
|
||||||
|
#include "oven_control.h"
|
||||||
|
#include <LiquidCrystal.h>
|
||||||
|
#include <DFR_Key.h>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// state: temp_min, temp_max, duration
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
// x0 = time of last_state.temp_max
|
||||||
|
// y0 = time of last_state.temp_max
|
||||||
|
|
||||||
|
// x1 = xa + state.duration
|
||||||
|
// y1 = state.temp_max
|
||||||
|
|
||||||
|
|
||||||
|
// x = actual_time
|
||||||
|
// y = y0 + ((x - x0) * y1 - (x - x0) * y0) / (x1 - x0)
|
||||||
|
|
||||||
|
// preheat, soak, tal1, soak, tal2, rampdown
|
||||||
|
|
||||||
|
|
||||||
|
Profile::Profile() :
|
||||||
|
data {150, // °C
|
||||||
|
200, // °C
|
||||||
|
217, // °C
|
||||||
|
260, // 245-260°C
|
||||||
|
480, // seconds
|
||||||
|
|
||||||
|
0, // ts ramp up rates
|
||||||
|
2,
|
||||||
|
0, // tp ramp up rates
|
||||||
|
2,
|
||||||
|
-1, // ramp down rates
|
||||||
|
-6,
|
||||||
|
|
||||||
|
// profile temp durations
|
||||||
|
60,
|
||||||
|
180,
|
||||||
|
60,
|
||||||
|
150,
|
||||||
|
20,
|
||||||
|
40},
|
||||||
|
config_index(0),
|
||||||
|
config_state(CS_MENU),
|
||||||
|
key(NO_KEY) {}
|
||||||
|
|
||||||
|
boolean Profile::handle_config_state() {
|
||||||
|
boolean changed = false;
|
||||||
|
|
||||||
|
key = keypad.getKey();
|
||||||
|
|
||||||
|
switch (config_state) {
|
||||||
|
case CS_MENU:
|
||||||
|
if (key == SELECT_KEY) {
|
||||||
|
config_state = CS_END;
|
||||||
|
}
|
||||||
|
else if (key > 0) {
|
||||||
|
config_state = CS_EDIT;
|
||||||
|
print_config_state();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CS_EDIT:
|
||||||
|
switch (key) {
|
||||||
|
case LEFT_KEY:
|
||||||
|
config_index = (config_index-1) % PI_END;
|
||||||
|
changed = true;
|
||||||
|
break;
|
||||||
|
case RIGHT_KEY:
|
||||||
|
config_index = (config_index+1) % PI_END;
|
||||||
|
changed = true;
|
||||||
|
break;
|
||||||
|
case UP_KEY:
|
||||||
|
data[config_index]++;
|
||||||
|
changed = true;
|
||||||
|
break;
|
||||||
|
case DOWN_KEY:
|
||||||
|
data[config_index]--;
|
||||||
|
changed = true;
|
||||||
|
break;
|
||||||
|
case SELECT_KEY:
|
||||||
|
config_state = CS_END;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
;
|
||||||
|
}
|
||||||
|
if (changed)
|
||||||
|
print_config_state();
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Profile::print_config_state_0() {
|
||||||
|
lcd.clear();
|
||||||
|
lcd.setCursor(0, 0);
|
||||||
|
lcd.print("start | config");
|
||||||
|
lcd.setCursor(0, 1);
|
||||||
|
lcd.print("[sel] | [other]");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Profile::print_config_state() {
|
||||||
|
lcd.clear();
|
||||||
|
switch (config_index) {
|
||||||
|
case PI_TS_MIN:
|
||||||
|
lcd.setCursor(0,0);
|
||||||
|
lcd.print("PI_TS_MIN: ");
|
||||||
|
lcd.print(data[PI_TS_MIN]);
|
||||||
|
break;
|
||||||
|
case PI_TS_MAX:
|
||||||
|
lcd.setCursor(0,0);
|
||||||
|
lcd.print("PI_TS_MAX: ");
|
||||||
|
lcd.print(data[PI_TS_MAX]);
|
||||||
|
break;
|
||||||
|
case PI_TL:
|
||||||
|
lcd.setCursor(0,0);
|
||||||
|
lcd.print("Tl: ");
|
||||||
|
lcd.print(data[PI_TL]);
|
||||||
|
break;
|
||||||
|
case PI_TP_MIN:
|
||||||
|
lcd.setCursor(0,0);
|
||||||
|
lcd.print("Tp min: ");
|
||||||
|
lcd.print(data[PI_TP_MIN]);
|
||||||
|
break;
|
||||||
|
case PI_TP_MAX:
|
||||||
|
lcd.setCursor(0,0);
|
||||||
|
lcd.print("Tp max: ");
|
||||||
|
lcd.print(data[PI_TP_MAX]);
|
||||||
|
break;
|
||||||
|
case PI_TIME_MAX:
|
||||||
|
lcd.setCursor(0,0);
|
||||||
|
lcd.print("time_max: ");
|
||||||
|
lcd.print(data[PI_TIME_MAX]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// PROFILE TEMP PER SECOND RATES
|
||||||
|
case PI_TS_RAMP_UP_MIN:
|
||||||
|
lcd.setCursor(0,0);
|
||||||
|
lcd.print("ramp_up_min: ");
|
||||||
|
lcd.print(data[PI_TS_RAMP_UP_MIN]);
|
||||||
|
break;
|
||||||
|
case PI_TS_RAMP_UP_MAX:
|
||||||
|
lcd.setCursor(0,0);
|
||||||
|
lcd.print("ramp_up_max: ");
|
||||||
|
lcd.print(data[PI_TS_RAMP_UP_MAX]);
|
||||||
|
break;
|
||||||
|
case PI_TP_RAMP_UP_MIN:
|
||||||
|
lcd.setCursor(0,0);
|
||||||
|
lcd.print("ramp_up_min: ");
|
||||||
|
lcd.print(data[PI_TP_RAMP_UP_MIN]);
|
||||||
|
break;
|
||||||
|
case PI_TP_RAMP_UP_MAX:
|
||||||
|
lcd.setCursor(0,0);
|
||||||
|
lcd.print("ramp_up_max: ");
|
||||||
|
lcd.print(data[PI_TP_RAMP_UP_MAX]);
|
||||||
|
break;
|
||||||
|
case PI_RAMP_DOWN_MAX:
|
||||||
|
lcd.setCursor(0,0);
|
||||||
|
lcd.print("ramp_down_min: ");
|
||||||
|
lcd.print(data[PI_RAMP_DOWN_MIN]);
|
||||||
|
break;
|
||||||
|
case PI_RAMP_DOWN_MIN:
|
||||||
|
lcd.setCursor(0,0);
|
||||||
|
lcd.print("ramp_down_max: ");
|
||||||
|
lcd.print(data[PI_RAMP_DOWN_MAX]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// PROFILE TEMP DURATIONS
|
||||||
|
case PI_TS_DURATION_MIN:
|
||||||
|
lcd.setCursor(0,0);
|
||||||
|
lcd.print("Ts_duration_min: ");
|
||||||
|
lcd.print(data[PI_TS_DURATION_MIN]);
|
||||||
|
break;
|
||||||
|
case PI_TS_DURATION_MAX:
|
||||||
|
lcd.setCursor(0,0);
|
||||||
|
lcd.print("Ts_duration_max: ");
|
||||||
|
lcd.print(data[PI_TS_DURATION_MAX]);
|
||||||
|
break;
|
||||||
|
case PI_TL_DURATION_MIN:
|
||||||
|
lcd.setCursor(0,0);
|
||||||
|
lcd.print("Tl_duration_min: ");
|
||||||
|
lcd.print(data[PI_TL_DURATION_MIN]);
|
||||||
|
break;
|
||||||
|
case PI_TL_DURATION_MAX:
|
||||||
|
lcd.setCursor(0,0);
|
||||||
|
lcd.print("Tl_duration_max: ");
|
||||||
|
lcd.print(data[PI_TL_DURATION_MAX]);
|
||||||
|
break;
|
||||||
|
case PI_TP_DURATION_MIN:
|
||||||
|
lcd.setCursor(0,0);
|
||||||
|
lcd.print("Tp_duration_min: ");
|
||||||
|
lcd.print(data[PI_TP_DURATION_MIN]);
|
||||||
|
break;
|
||||||
|
case PI_TP_DURATION_MAX:
|
||||||
|
lcd.setCursor(0,0);
|
||||||
|
lcd.print("Tp_duration_max: ");
|
||||||
|
lcd.print(data[PI_TP_DURATION_MAX]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_std_profile() {
|
||||||
|
// ProfileState * ps = &states[0];
|
||||||
|
// ps->temp_min = 150;
|
||||||
|
// ps->duration = -1;
|
||||||
|
//
|
||||||
|
// ps = &states[1];
|
||||||
|
// ps->temp_min = 200;
|
||||||
|
// ps->duration = 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
Profile2 std_profile = Profile2();
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
#ifndef _H_PROFILE
|
||||||
|
#define _H_PROFILE
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
class LiquidCrystal;
|
||||||
|
class DFR_Key;
|
||||||
|
|
||||||
|
#define PI_TS_MIN 0
|
||||||
|
#define PI_TS_MAX 1
|
||||||
|
|
||||||
|
#define PI_TL 2
|
||||||
|
|
||||||
|
#define PI_TP_MIN 3
|
||||||
|
#define PI_TP_MAX 4
|
||||||
|
#define PI_TIME_MAX 5
|
||||||
|
|
||||||
|
// profile temp per second rates
|
||||||
|
#define PI_TS_RAMP_UP_MIN 6
|
||||||
|
#define PI_TS_RAMP_UP_MAX 7
|
||||||
|
#define PI_TP_RAMP_UP_MIN 8
|
||||||
|
#define PI_TP_RAMP_UP_MAX 9
|
||||||
|
#define PI_RAMP_DOWN_MIN 10
|
||||||
|
#define PI_RAMP_DOWN_MAX 11
|
||||||
|
|
||||||
|
// profile temp durations
|
||||||
|
#define PI_TS_DURATION_MIN 12
|
||||||
|
#define PI_TS_DURATION_MAX 13
|
||||||
|
#define PI_TL_DURATION_MIN 14
|
||||||
|
#define PI_TL_DURATION_MAX 15
|
||||||
|
#define PI_TP_DURATION_MIN 16
|
||||||
|
#define PI_TP_DURATION_MAX 17
|
||||||
|
#define PI_END 18
|
||||||
|
|
||||||
|
// config states
|
||||||
|
#define CS_MENU 0
|
||||||
|
#define CS_EDIT 1
|
||||||
|
#define CS_END 2
|
||||||
|
|
||||||
|
class LiquidCrystal;
|
||||||
|
class DFR_Key;
|
||||||
|
|
||||||
|
extern LiquidCrystal lcd;
|
||||||
|
extern DFR_Key keypad;
|
||||||
|
|
||||||
|
class Profile {
|
||||||
|
public:
|
||||||
|
int data[18];
|
||||||
|
|
||||||
|
unsigned int config_index;
|
||||||
|
int config_state;
|
||||||
|
int key;
|
||||||
|
|
||||||
|
Profile();
|
||||||
|
boolean handle_config_state();
|
||||||
|
void print_config_state();
|
||||||
|
void print_config_state_0();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class ProfileState {
|
||||||
|
public:
|
||||||
|
int temp_min;
|
||||||
|
int duration;
|
||||||
|
int temp_max;
|
||||||
|
String name;
|
||||||
|
|
||||||
|
ProfileState() : temp_min(0), duration(0), temp_max(0) {}
|
||||||
|
ProfileState(int min, int dur, int max = -1, const char & name = NULL) : temp_min(min), duration(dur), temp_max(max), name(name) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
class SolderType {};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class Profile2 {
|
||||||
|
public:
|
||||||
|
ProfileState states[3];
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
extern Profile2 std_profile;
|
||||||
|
|
||||||
|
void set_std_profile();
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,172 @@
|
||||||
|
import sys, os, random
|
||||||
|
from PyQt4 import QtGui, QtCore
|
||||||
|
|
||||||
|
from numpy import arange, sin, pi, array, linspace
|
||||||
|
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
|
||||||
|
from matplotlib.figure import Figure
|
||||||
|
from matplotlib.path import Path
|
||||||
|
import matplotlib.patches as patches
|
||||||
|
|
||||||
|
from scipy.interpolate import *
|
||||||
|
|
||||||
|
progname = os.path.basename(sys.argv[0])
|
||||||
|
progversion = "0.1"
|
||||||
|
|
||||||
|
|
||||||
|
class MyMplCanvas(FigureCanvas):
|
||||||
|
"""Ultimately, this is a QWidget (as well as a FigureCanvasAgg, etc.)."""
|
||||||
|
def __init__(self, parent=None, width=5, height=4, dpi=100):
|
||||||
|
fig = Figure(figsize=(width, height), dpi=dpi)
|
||||||
|
self.axes = fig.add_subplot(111)
|
||||||
|
# We want the axes cleared every time plot() is called
|
||||||
|
self.axes.hold(False)
|
||||||
|
|
||||||
|
self.compute_initial_figure()
|
||||||
|
|
||||||
|
#
|
||||||
|
FigureCanvas.__init__(self, fig)
|
||||||
|
self.setParent(parent)
|
||||||
|
|
||||||
|
FigureCanvas.setSizePolicy(self,
|
||||||
|
QtGui.QSizePolicy.Expanding,
|
||||||
|
QtGui.QSizePolicy.Expanding)
|
||||||
|
FigureCanvas.updateGeometry(self)
|
||||||
|
|
||||||
|
def compute_initial_figure(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class MyDynamicMplCanvas(MyMplCanvas):
|
||||||
|
"""A canvas that updates itself every second with a new plot."""
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
MyMplCanvas.__init__(self, *args, **kwargs)
|
||||||
|
timer = QtCore.QTimer(self)
|
||||||
|
QtCore.QObject.connect(timer, QtCore.SIGNAL("timeout()"), self.update_figure)
|
||||||
|
timer.start(1000)
|
||||||
|
|
||||||
|
def compute_initial_figure(self):
|
||||||
|
x1_min = 150-20.
|
||||||
|
y1_min = 150.
|
||||||
|
|
||||||
|
x2_min = x1_min + 100.
|
||||||
|
y2_min = 200.
|
||||||
|
|
||||||
|
x3_min = x2_min + 20.
|
||||||
|
y3_min = 220.
|
||||||
|
|
||||||
|
x4_min = x3_min + 249. - y3_min
|
||||||
|
y4_min = 249.
|
||||||
|
|
||||||
|
x5_min = x4_min + (y4_min - y3_min) / 3 * 2
|
||||||
|
y5_min = y3_min
|
||||||
|
|
||||||
|
x6_min = x5_min + y5_min-20.
|
||||||
|
y6_min = 20.
|
||||||
|
|
||||||
|
p1x = array([0., x1_min, x2_min, x3_min, x4_min, x5_min, x6_min])
|
||||||
|
p1y = array([20., y1_min, y2_min, y3_min, y4_min, y5_min, y6_min])
|
||||||
|
|
||||||
|
interp = pchip(p1x, p1y)
|
||||||
|
|
||||||
|
xx = linspace(0., x6_min, x6_min)
|
||||||
|
|
||||||
|
ynew = interp(xx)
|
||||||
|
|
||||||
|
print "len xx", len(xx)
|
||||||
|
print "len p1x", len(p1x)
|
||||||
|
print "xy", zip(p1x, p1y)
|
||||||
|
print "ynew", ynew
|
||||||
|
|
||||||
|
dtp_min = 99999.
|
||||||
|
dtp_max = 0.
|
||||||
|
dtpi = -1
|
||||||
|
|
||||||
|
dtn_min = -99999.
|
||||||
|
dtn_max = 0.
|
||||||
|
dtni = -1
|
||||||
|
for i in xrange(1, len(ynew)):
|
||||||
|
tmp = ynew[i] - ynew[i-1]
|
||||||
|
if tmp > 0:
|
||||||
|
if tmp < dtp_min:
|
||||||
|
dtp_min = tmp
|
||||||
|
dtpi = i
|
||||||
|
elif tmp > dtp_max:
|
||||||
|
dtp_max = tmp
|
||||||
|
dtpi = i
|
||||||
|
elif tmp < 0:
|
||||||
|
if tmp > dtn_min:
|
||||||
|
dtn_min = tmp
|
||||||
|
dtni = i
|
||||||
|
elif tmp < dtn_max:
|
||||||
|
dtn_max = tmp
|
||||||
|
dtni = i
|
||||||
|
print "max negative", dtn_min, dtn_max, dtni
|
||||||
|
print "max positive", dtp_min, dtp_max, dtpi
|
||||||
|
|
||||||
|
self.axes.plot(p1x, p1y, "bo", xx, ynew, "r-")
|
||||||
|
#self.axes.plot(p1x, p1y, 'r-o')
|
||||||
|
|
||||||
|
def update_figure(self):
|
||||||
|
# Build a list of 4 random integers between 0 and 10 (both inclusive)
|
||||||
|
#l = [ random.randint(0, 10) for i in xrange(4) ]
|
||||||
|
|
||||||
|
#self.axes.plot([0, 1, 2, 3], l, 'r')
|
||||||
|
#self.draw()
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationWindow(QtGui.QMainWindow):
|
||||||
|
def __init__(self):
|
||||||
|
QtGui.QMainWindow.__init__(self)
|
||||||
|
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
|
||||||
|
self.setWindowTitle("application main window")
|
||||||
|
|
||||||
|
self.file_menu = QtGui.QMenu('&File', self)
|
||||||
|
self.file_menu.addAction('&Quit', self.fileQuit,
|
||||||
|
QtCore.Qt.CTRL + QtCore.Qt.Key_Q)
|
||||||
|
self.menuBar().addMenu(self.file_menu)
|
||||||
|
|
||||||
|
self.help_menu = QtGui.QMenu('&Help', self)
|
||||||
|
self.menuBar().addSeparator()
|
||||||
|
self.menuBar().addMenu(self.help_menu)
|
||||||
|
|
||||||
|
self.help_menu.addAction('&About', self.about)
|
||||||
|
|
||||||
|
self.main_widget = QtGui.QWidget(self)
|
||||||
|
|
||||||
|
l = QtGui.QVBoxLayout(self.main_widget)
|
||||||
|
#sc = MyStaticMplCanvas(self.main_widget, width=5, height=4, dpi=100)
|
||||||
|
dc = MyDynamicMplCanvas(self.main_widget, width=5, height=4, dpi=100)
|
||||||
|
#l.addWidget(sc)
|
||||||
|
l.addWidget(dc)
|
||||||
|
|
||||||
|
self.main_widget.setFocus()
|
||||||
|
self.setCentralWidget(self.main_widget)
|
||||||
|
|
||||||
|
self.statusBar().showMessage("All hail matplotlib!", 2000)
|
||||||
|
|
||||||
|
def fileQuit(self):
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
def closeEvent(self, ce):
|
||||||
|
self.fileQuit()
|
||||||
|
|
||||||
|
def about(self):
|
||||||
|
QtGui.QMessageBox.about(self, "About %s" % progname,
|
||||||
|
u"""%(prog)s version %(version)s
|
||||||
|
Copyright \N{COPYRIGHT SIGN} 2005 Florent Rougon, 2006 Darren Dale
|
||||||
|
|
||||||
|
This program is a simple example of a Qt4 application embedding matplotlib
|
||||||
|
canvases.
|
||||||
|
|
||||||
|
It may be used and modified with no restriction; raw copies as well as
|
||||||
|
modified versions may be distributed without limitation."""
|
||||||
|
% {"prog": progname, "version": progversion})
|
||||||
|
|
||||||
|
|
||||||
|
qApp = QtGui.QApplication(sys.argv)
|
||||||
|
|
||||||
|
aw = ApplicationWindow()
|
||||||
|
aw.setWindowTitle("%s" % progname)
|
||||||
|
aw.show()
|
||||||
|
sys.exit(qApp.exec_())
|
Binary file not shown.
After Width: | Height: | Size: 8.5 KiB |
Loading…
Reference in New Issue