Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -245,12 +245,14 @@ void startIteration() {
void setSwitchPositions() {
int i;
int switchPosition = (blown) ? 0 : 1;
// motor protection switch transitions instantly (d_position is 0 or 1)
double d = 1 - switchPosition;
for (i = 0; i != sim.elmList.size(); i++) {
Object o = sim.elmList.elementAt(i);
if (o instanceof RelayContactElm) {
RelayContactElm s2 = (RelayContactElm) o;
if (s2.label.equals(label))
s2.setPosition(switchPosition, RelayCoilElm.TYPE_NORMAL);
s2.setPosition(switchPosition, d, RelayCoilElm.TYPE_NORMAL);
}
}
}
Expand Down
43 changes: 35 additions & 8 deletions src/com/lushprojects/circuitjs1/client/RelayCoilElm.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ class RelayCoilElm extends CircuitElm {
double coilCurrent, coilCurCount;
double avgCurrent;

// fractional position, between 0 and 1 inclusive
// binary position: 0 = de-energized, 1 = energized
// changes as hard step after switching delay expires
double d_position;

// integer position, can be 0 (off), 1 (on), 2 (in between)
Expand All @@ -46,6 +47,10 @@ class RelayCoilElm extends CircuitElm {

// time to switch in seconds
double switchingTime;
// release time (0 = same as switchingTime). real relays typically
// release faster than they engage because the spring force assists
// the armature return.
double releaseTime;
double switchingTimeOn, switchingTimeOff;
double lastTransition;

Expand Down Expand Up @@ -121,6 +126,8 @@ void dumpXml(Document doc, Element elem) {
XMLSerializer.dumpAttr(elem, "cr", coilR);
XMLSerializer.dumpAttr(elem, "ofc", offCurrent);
XMLSerializer.dumpAttr(elem, "swt", switchingTime);
if (releaseTime > 0)
XMLSerializer.dumpAttr(elem, "rt", releaseTime);
XMLSerializer.dumpAttr(elem, "tp", type);
}
void dumpXmlState(Document doc, Element elem) {
Expand All @@ -136,6 +143,7 @@ void undumpXml(XMLDeserializer xml) {
coilR = xml.parseDoubleAttr("cr", coilR);
offCurrent = xml.parseDoubleAttr("ofc", offCurrent);
switchingTime = xml.parseDoubleAttr("swt", switchingTime);
releaseTime = xml.parseDoubleAttr("rt", 0);
type = xml.parseIntAttr("tp", type);
coilCurrent = xml.parseDoubleAttr("ci", coilCurrent);
state = xml.parseIntAttr("st", state);
Expand Down Expand Up @@ -271,14 +279,16 @@ void stamp() {
// resistor from internal node to coil post 2
sim.stampResistor(nodes[nCoil3], nodes[nCoil2], coilR);

double effectiveReleaseTime = (releaseTime > 0) ? releaseTime : switchingTime;
if (type == TYPE_ON_DELAY) {
switchingTimeOn = switchingTime;
switchingTimeOff = 0;
} else if (type == TYPE_OFF_DELAY) {
switchingTimeOff = switchingTime;
switchingTimeOn = 0;
} else {
switchingTimeOff = switchingTimeOn = switchingTime;
switchingTimeOn = switchingTime;
switchingTimeOff = effectiveReleaseTime;
}
setSwitchPositions();
}
Expand All @@ -289,7 +299,7 @@ void startIteration() {
double a = Math.exp(-sim.timeStep*1e3);
avgCurrent = a*avgCurrent + (1-a)*absCurrent;
int oldSwitchPosition = switchPosition;

if (state == 0) {
if (avgCurrent > onCurrent) {
lastTransition = sim.t;
Expand All @@ -311,15 +321,23 @@ else if (sim.t-lastTransition > switchingTimeOn) {
state = 3;
}
} else if (state == 3) {
if (avgCurrent > onCurrent)
if (avgCurrent > onCurrent)
state = 2;
else if (sim.t-lastTransition > switchingTimeOff) {
state = 0;
if (type != TYPE_LATCHING)
switchPosition = 0;
}
}


// d_position tracks the binary contact state as a hard step.
// During the switching delay (states 1 and 3), d_position stays
// at its old value. It snaps to the new value only when the
// delay expires and switchPosition actually changes. This
// produces realistic inductive voltage spikes when relay contacts
// open, rather than suppressing them with smooth interpolation.
d_position = switchPosition;

if (oldSwitchPosition != switchPosition)
setSwitchPositions();
}
Expand All @@ -334,7 +352,7 @@ void setSwitchPositions() {
if (o instanceof RelayContactElm) {
RelayContactElm s2 = (RelayContactElm) o;
if (s2.label.equals(label))
s2.setPosition(1-switchPosition, type);
s2.setPosition(1-switchPosition, d_position, type);
}
}
}
Expand Down Expand Up @@ -381,15 +399,21 @@ public EditInfo getEditInfo(int n) {
return new EditInfo("Coil Resistance (ohms)", coilR, 0, 0).setPositive();
if (n == 5)
return new EditInfo("Switching Time (s)", switchingTime, 0, 0).setPositive();
if (n == 6)
if (n == 6 && (type == TYPE_NORMAL || type == TYPE_LATCHING))
return new EditInfo("Release Time (s)", releaseTime > 0 ? releaseTime : switchingTime, 0, 0).setPositive();
int labelIdx = (type == TYPE_NORMAL || type == TYPE_LATCHING) ? 7 : 6;
if (n == labelIdx)
return new EditInfo("Label (for linking)", label);
return null;
}

public void setEditValue(int n, EditInfo ei) {
if (n == 0) {
int oldType = type;
type = ei.choice.getSelectedIndex();
setPoints();
if (oldType != type)
ei.newDialog = true;
}
if (n == 1 && ei.value > 0) {
inductance = ei.value;
Expand All @@ -403,7 +427,10 @@ public void setEditValue(int n, EditInfo ei) {
coilR = ei.value;
if (n == 5 && ei.value > 0)
switchingTime = ei.value;
if (n == 6)
if (n == 6 && (type == TYPE_NORMAL || type == TYPE_LATCHING) && ei.value > 0)
releaseTime = ei.value;
int labelIdx = (type == TYPE_NORMAL || type == TYPE_LATCHING) ? 7 : 6;
if (n == labelIdx)
label = ei.textf.getText();
}

Expand Down
57 changes: 33 additions & 24 deletions src/com/lushprojects/circuitjs1/client/RelayContactElm.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,10 @@ class RelayContactElm extends CircuitElm {
final int FLAG_IEC = 4;
int type;

// fractional position, between 0 and 1 inclusive
// double d_position;

// binary contact position: 0 = closed (r_on), 1 = open (r_off)
// changes as a hard step after the relay's switching delay expires
double d_position;

// integer position, can be 0 (off), 1 (on), 2 (in between)
int i_position;

Expand Down Expand Up @@ -103,7 +104,7 @@ void draw(Graphics g) {
drawThickLine(g, swposts[i], swpoles[i]);
}

interpPoint(swpoles[1], swpoles[2], ptSwitch, i_position);
interpPoint(swpoles[1], swpoles[2], ptSwitch, d_position);
//setVoltageColor(g, volts[nSwitch0]);
g.setColor(Color.lightGray);
drawThickLine(g, swpoles[0], ptSwitch);
Expand All @@ -120,8 +121,8 @@ void draw(Graphics g) {

if (useIECSymbol() && (type == RelayCoilElm.TYPE_ON_DELAY || type == RelayCoilElm.TYPE_OFF_DELAY)) {
g.setColor(Color.lightGray);
interpPoint(lead1, lead2, extraPoints[0], .5-2/32., i_position == 1 ? openhs/2 : 0);
interpPoint(lead1, lead2, extraPoints[1], .5+2/32., i_position == 1 ? openhs/2 : 0);
interpPoint(lead1, lead2, extraPoints[0], .5-2/32., d_position * openhs/2);
interpPoint(lead1, lead2, extraPoints[1], .5+2/32., d_position * openhs/2);
g.drawLine(extraPoints[0], extraPoints[2]);
g.drawLine(extraPoints[1], extraPoints[3]);
g.context.beginPath();
Expand All @@ -139,9 +140,9 @@ void draw(Graphics g) {

switchCurCount = updateDotCount(switchCurrent, switchCurCount);
drawDots(g, swposts[0], swpoles[0], switchCurCount);
if (i_position == 0)
drawDots(g, swpoles[i_position+1], swposts[i_position+1], switchCurCount);

if (d_position < .5)
drawDots(g, swpoles[1], swposts[1], switchCurCount);

drawPosts(g);
setBbox(point1, point2, openhs);
Expand All @@ -150,7 +151,7 @@ void draw(Graphics g) {
double getCurrentIntoNode(int n) {
if (n == 0)
return -switchCurrent;
if (n == 1+i_position)
if (n == 1)
return switchCurrent;
return 0;
}
Expand Down Expand Up @@ -185,9 +186,17 @@ void setPoints() {
}
}

public void setPosition(int i_position_, int type_) {
public void setPosition(int i_position_, double coil_d_position, int type_) {
i_position = (isNormallyClosed()) ? (1-i_position_) : i_position_;
type = type_;
// compute binary contact position (0 = closed/r_on, 1 = open/r_off)
// coil_d_position is binary (0 or 1), snaps after switching delay.
// NO contact: closed when energized (coil_d=1 -> contact_d=0)
// NC contact: open when energized (coil_d=1 -> contact_d=1)
if (isNormallyClosed())
d_position = coil_d_position;
else
d_position = 1 - coil_d_position;
}

boolean isNormallyClosed() { return (flags & FLAG_NORMALLY_CLOSED) != 0; }
Expand All @@ -200,6 +209,8 @@ void reset() {
super.reset();
switchCurrent = switchCurCount = 0;
i_position = 0;
// NO contacts are open at rest (d_position=1), NC contacts are closed (d_position=0)
d_position = isNormallyClosed() ? 0 : 1;

// preserve onState because if we don't, Relay Flip-Flop gets left in a weird state on reset.
// onState = false;
Expand All @@ -214,24 +225,22 @@ void stamp() {
boolean nonLinear() { return true; }

void doStep() {
sim.stampResistor(nodes[nSwitch0], nodes[nSwitch1], i_position == 0 ? r_on : r_off);
// d_position is binary: 0 = closed (r_on), 1 = open (r_off).
// Hard step produces realistic inductive voltage spikes.
double resistance = d_position > .5 ? r_off : r_on;
sim.stampResistor(nodes[nSwitch0], nodes[nSwitch1], resistance);
}
void calculateCurrent() {
// actually this isn't correct, since there is a small amount
// of current through the switch when off
if (i_position == 1)
switchCurrent = 0;
else
switchCurrent = (volts[nSwitch0]-volts[nSwitch1+i_position])/r_on;
double resistance = d_position > .5 ? r_off : r_on;
switchCurrent = (volts[nSwitch0]-volts[nSwitch1]) / resistance;
}
String getElmType() { return "relay"; }
void getInfo(String arr[]) {
arr[0] = Locale.LS("relay");
if (i_position == 0)
arr[0] += " (" + Locale.LS("off") + ")";
else if (i_position == 1)
arr[0] += " (" + Locale.LS("on") + ")";
int i;
arr[0] = Locale.LS("relay contact");
if (d_position < .5)
arr[0] += " (" + Locale.LS("closed") + ")";
else
arr[0] += " (" + Locale.LS("open") + ")";
int ln = 1;
arr[ln++] = "I = " + getCurrentDText(switchCurrent);
}
Expand Down
Loading