This commit is contained in:
Stefan Kögl 2012-11-10 15:27:43 +01:00
parent eb7b4a3060
commit 8521834143
16 changed files with 1219 additions and 1072 deletions

1
.gitignore vendored
View File

@ -1,4 +1,5 @@
*~
*.pyc
build
CMakeCache.txt
CMakeFiles

View File

@ -4,12 +4,34 @@ Lots of help from here:
http://stackoverflow.com/questions/1093598/pyserial-how-to-read-last-line-sent-from-serial-device
"""
from threading import Thread
import time
import serial
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_MAX = 1
@ -17,8 +39,10 @@ PI_TL = 2
PI_TP = 3
PI_TIME_MAX = 4
PI_RAMP_UP_MIN = 5
PI_RAMP_UP_MAX = 6
PI_TS_RAMP_UP_MIN = 5
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_MAX = 8
@ -29,19 +53,29 @@ PI_TL_DURATION_MAX = 12
PI_TP_DURATION_MIN = 13
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):
global last_received
buffer = ''
ser.write(chr(255))
global status
try:
time.sleep(2)
recv_config(ser)
except Exception, e:
print e
pass
while 1:
ser.write(chr(254))
ser.flush()
profile = struct.unpack("hhhhhhhhhhhhhhh", ser.read(30))
status = struct.unpack("hhhhhb", ser.read(11))
ser.flushInput()
while 1:
ser.write(chr(254))
ser.flush()
last_received = ser.read(11)
print repr(last_received)
ser.flushInput()
class SerialData(object):
@ -57,15 +91,31 @@ class SerialData(object):
Thread(target=receiving, args=(self.ser,)).start()
def next(self):
global status
if not self.ser:
return 100 #return anything so we can test when Arduino isn't connected
return status[1]
try:
return int(struct.unpack("hhhhhb", last_received)[1])
except Exception, e:
print e
return 0
return status[1]
except Exception:
pass
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):
if self.ser:

View File

@ -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
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
SKETCH reflowctl
SRCS oven_control.cpp profile.cpp main.cpp
HDRS oven_control.h profile.h
PORT /dev/ttyUSB0
BOARD atmega328)

BIN
burn.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -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;
}
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -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

View File

@ -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

251
oven_control.cpp Normal file
View File

@ -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;
}
}

139
oven_control.h Normal file
View File

@ -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
View File

@ -1,30 +1,5 @@
# -*- 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 pprint
import random
@ -33,85 +8,40 @@ import wx
REFRESH_INTERVAL_MS = 1000
# The recommended way to use wx with mpl is with the WXAgg
# backend.
#
import matplotlib
matplotlib.use('WXAgg')
import matplotlib.lines
from matplotlib.figure import Figure
from matplotlib.pyplot import legend
from matplotlib.backends.backend_wxagg import \
FigureCanvasWxAgg as FigCanvas, \
NavigationToolbar2WxAgg as NavigationToolbar
from matplotlib.path import Path
import matplotlib.patches as patches
import wx.lib.buttons as buttons
import numpy as np
import pylab
#Data comes from here
from Arduino_Monitor import SerialData as DataGen
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
import Arduino_Monitor
class GraphFrame(wx.Frame):
""" The main frame of the application
"""
title = 'Demo: dynamic matplotlib graph'
title = 'reflowctl gui'
def __init__(self):
wx.Frame.__init__(self, None, -1, self.title)
self.datagen = DataGen()
self.data = [self.datagen.next()]
self.paused = False
self.started = False
self.profile = []
self.state = []
self.count = 0
self.create_menu()
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.redraw_timer.Start(REFRESH_INTERVAL_MS)
def create_menu(self):
self.menubar = wx.MenuBar()
@ -137,110 +68,274 @@ class GraphFrame(wx.Frame):
def create_main_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.canvas = FigCanvas(self.panel, -1, self.fig)
self.xmin_control = BoundControlBox(self.panel, -1, "X min", 0)
self.xmax_control = BoundControlBox(self.panel, -1, "X max", 250)
self.ymin_control = BoundControlBox(self.panel, -1, "Y min", 0)
self.ymax_control = BoundControlBox(self.panel, -1, "Y max", 280)
self.recv_config_button = wx.Button(self.panel, -1, "Receive Config")
self.Bind(wx.EVT_BUTTON, self.on_recv_config_button, self.recv_config_button)
self.pause_button = wx.Button(self.panel, -1, "Pause")
self.Bind(wx.EVT_BUTTON, self.on_pause_button, self.pause_button)
self.Bind(wx.EVT_UPDATE_UI, self.on_update_pause_button, self.pause_button)
self.send_button = wx.Button(self.panel, -1, "Send Config")
self.Bind(wx.EVT_BUTTON, self.on_send_button, self.send_button)
self.cb_grid = wx.CheckBox(self.panel, -1,
"Show Grid",
style=wx.ALIGN_RIGHT)
self.Bind(wx.EVT_CHECKBOX, self.on_cb_grid, self.cb_grid)
self.cb_grid.SetValue(True)
self.start_button = buttons.GenToggleButton(self.panel, -1, "Start")
self.Bind(wx.EVT_BUTTON, self.on_start_button, self.start_button)
self.cb_xlab = wx.CheckBox(self.panel, -1,
"Show X labels",
style=wx.ALIGN_RIGHT)
self.Bind(wx.EVT_CHECKBOX, self.on_cb_xlab, self.cb_xlab)
self.cb_xlab.SetValue(True)
#self.on_bitmap = wx.Image('burn.png', wx.BITMAP_TYPE_PNG).Scale(32, 32, wx.IMAGE_QUALITY_HIGH).ConvertToBitmap()
#self.off_bitmap = wx.Image('unburn.png', wx.BITMAP_TYPE_PNG).ConvertToBitmap()
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.hbox2.Add(self.xmin_control, border=5, flag=wx.ALL)
self.hbox2.Add(self.xmax_control, border=5, flag=wx.ALL)
self.hbox2.AddSpacer(24)
self.hbox2.Add(self.ymin_control, border=5, flag=wx.ALL)
self.hbox2.Add(self.ymax_control, border=5, flag=wx.ALL)
self.ctrls = wx.BoxSizer(wx.VERTICAL)
self.ctrls.Add(self.recv_config_button, border=5, flag=wx.ALL | wx.ALIGN_LEFT)
self.ctrls.Add(self.send_button, border=5, flag=wx.ALL | wx.ALIGN_LEFT)
self.ctrls.Add(self.start_button, border=5, flag=wx.ALL | wx.ALIGN_LEFT)
self.hbox1.Add(self.ctrls, border=5, flag=wx.ALL | wx.ALIGN_TOP)
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.hbox1, 0, flag=wx.ALIGN_LEFT | wx.TOP)
self.vbox.Add(self.hbox2, 0, flag=wx.ALIGN_LEFT | wx.TOP)
#self.vbox.Add(self.hbox1, 0, flag=wx.ALIGN_LEFT | wx.TOP)
self.panel.SetSizer(self.vbox)
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):
self.statusbar = self.CreateStatusBar()
def init_plot(self):
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.set_axis_bgcolor('black')
self.axes.set_title(u'Reflow Temperature', 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_yticklabels(), fontsize=8)
# plot the data as a line series, and save the reference
# to the plotted line series
#
# no 1
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_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_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])
ts_min_y_max = ts_min
# no t2
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]
tl_x_min = Arduino_Monitor.profile[Arduino_Monitor.PI_TL]
# no 10
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]
tl_y_min = Arduino_Monitor.profile[Arduino_Monitor.PI_TL]
# no 4
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]
tl_y_max = Arduino_Monitor.profile[Arduino_Monitor.PI_TL]
# no 5
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 = [
[ 0.0, 0.0],
[ 75.0, 150.0],
[100.0, 200.0],
[108.5, 217.0],
[130.0, 260.0],
[170.0, 260.0],
[300.0, 0.0],
[ 0.0, 0.0]]
[0.0, 0.0],
[ts_min_x_min, ts_min_y_min],
[ts_max_x_min, ts_max_y_min],
[ts_max_x_max, ts_max_y_max],
[ts_min_x_max, ts_min_y_max],
#[tp_x_min, tp_y_min],
#[tp_x_max, tp_y_max],
#[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,
Path.LINETO,
codes = [
Path.MOVETO,
Path.LINETO,
Path.LINETO,
Path.LINETO,
Path.LINETO,
Path.LINETO,
#Path.LINETO,
#Path.LINETO,
#Path.LINETO,
#Path.LINETO,
Path.CLOSEPOLY]
self.plot_data = self.axes.plot(
@ -249,86 +344,62 @@ class GraphFrame(wx.Frame):
color=(1, 1, 0),
)[0]
print "verts", verts
path = Path(verts, codes)
self.patch = patches.PathPatch(path, edgecolor="red", facecolor='orange', lw=2)
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):
""" 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():
xmin = xmax - 50
else:
xmin = int(self.xmin_control.manual_value())
self.axes.set_xbound(lower=0, upper=self.xmax)
self.axes.set_ybound(lower=0, upper=self.ymax)
#xmax = 480
self.axes.grid(True, color='gray')
# for ymin and ymax, find the minimal and maximal values
# 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())
pylab.setp(self.axes.get_xticklabels(), visible=True)
self.plot_data.set_xdata(np.arange(len(self.data)))
self.plot_data.set_ydata(np.array(self.data))
self.canvas.draw()
def on_pause_button(self, event):
self.paused = not self.paused
def update_config(self):
for ix, i in enumerate(self.profile):
i.SetValue(str(Arduino_Monitor.profile[i]))
def on_update_pause_button(self, event):
label = "Resume" if self.paused else "Pause"
self.pause_button.SetLabel(label)
def update_state(self):
if Arduino_Monitor.status[3] > 0:
self.started = True
def on_cb_grid(self, event):
self.draw_plot()
self.time.SetValue(str(Arduino_Monitor.status[0]))
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):
file_choices = "PNG (*.png)|*.png"
@ -347,12 +418,11 @@ class GraphFrame(wx.Frame):
self.flash_status_message("Saved to %s" % path)
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 not self.paused:
if self.started:
self.data.append(self.datagen.next())
self.update_state()
self.draw_plot()
def on_exit(self, event):
@ -376,3 +446,4 @@ if __name__ == '__main__':
app.frame = GraphFrame()
app.frame.Show()
app.MainLoop()

219
profile.cpp Normal file
View File

@ -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();

87
profile.h Normal file
View File

@ -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

172
qtplot.py Normal file
View File

@ -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_())

BIN
unburn.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB