summaryrefslogtreecommitdiff
path: root/ldmicro/simulate.cpp
diff options
context:
space:
mode:
authorakshay-c2019-01-30 12:23:44 +0530
committerakshay-c2019-01-30 12:23:44 +0530
commit4196481f74afb84e5cc59cdf00c06c1ca1becab7 (patch)
treeb531deb0466897691f08f9076b7012592f026664 /ldmicro/simulate.cpp
downloadLDmicroQt-4196481f74afb84e5cc59cdf00c06c1ca1becab7.tar.gz
LDmicroQt-4196481f74afb84e5cc59cdf00c06c1ca1becab7.tar.bz2
LDmicroQt-4196481f74afb84e5cc59cdf00c06c1ca1becab7.zip
First commit
Diffstat (limited to 'ldmicro/simulate.cpp')
-rw-r--r--ldmicro/simulate.cpp992
1 files changed, 992 insertions, 0 deletions
diff --git a/ldmicro/simulate.cpp b/ldmicro/simulate.cpp
new file mode 100644
index 0000000..59e7b6e
--- /dev/null
+++ b/ldmicro/simulate.cpp
@@ -0,0 +1,992 @@
+//-----------------------------------------------------------------------------
+// Copyright 2007 Jonathan Westhues
+//
+// This file is part of LDmicro.
+//
+// LDmicro is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// LDmicro is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with LDmicro. If not, see <http://www.gnu.org/licenses/>.
+//------
+//
+// Routines to simulate the logic interactively, for testing purposes. We can
+// simulate in real time, triggering off a Windows timer, or we can
+// single-cycle it. The GUI acts differently in simulation mode, to show the
+// status of all the signals graphically, show how much time is left on the
+// timers, etc.
+// Jonathan Westhues, Nov 2004
+//-----------------------------------------------------------------------------
+#include "linuxUI.h"
+//#include <commctrl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+
+#include "ldmicro.h"
+#include "intcode.h"
+#include "freezeLD.h"
+
+static struct {
+ char name[MAX_NAME_LEN];
+ BOOL powered;
+} SingleBitItems[MAX_IO];
+static int SingleBitItemsCount;
+
+static struct {
+ char name[MAX_NAME_LEN];
+ SWORD val;
+ DWORD usedFlags;
+} Variables[MAX_IO];
+static int VariablesCount;
+
+static struct {
+ char name[MAX_NAME_LEN];
+ SWORD val;
+} AdcShadows[MAX_IO];
+static int AdcShadowsCount;
+
+#define VAR_FLAG_TON 0x00000001
+#define VAR_FLAG_TOF 0x00000002
+#define VAR_FLAG_RTO 0x00000004
+#define VAR_FLAG_CTU 0x00000008
+#define VAR_FLAG_CTD 0x00000010
+#define VAR_FLAG_CTC 0x00000020
+#define VAR_FLAG_RES 0x00000040
+#define VAR_FLAG_ANY 0x00000080
+
+#define VAR_FLAG_OTHERWISE_FORGOTTEN 0x80000000
+
+
+// Schematic-drawing code needs to know whether we're in simulation mode or
+// note, as that changes how everything is drawn; also UI code, to disable
+// editing during simulation.
+BOOL InSimulationMode;
+
+// Don't want to redraw the screen unless necessary; track whether a coil
+// changed state or a timer output switched to see if anything could have
+// changed (not just coil, as we show the intermediate steps too).
+static BOOL NeedRedraw;
+// Have to let the effects of a coil change in cycle k appear in cycle k+1,
+// or set by the UI code to indicate that user manually changed an Xfoo
+// input.
+BOOL SimulateRedrawAfterNextCycle;
+
+// Don't want to set a timer every 100 us to simulate a 100 us cycle
+// time...but we can cycle multiple times per timer interrupt and it will
+// be almost as good, as long as everything runs fast.
+static int CyclesPerTimerTick;
+
+// Program counter as we evaluate the intermediate code.
+static int IntPc;
+
+// A window to allow simulation with the UART stuff (insert keystrokes into
+// the program, view the output, like a terminal window).
+static HWID UartSimulationWindow;
+static HWID UartSimulationTextControl;
+static LONG_PTR PrevTextProc;
+
+static int QueuedUartCharacter = -1;
+static int SimulateUartTxCountdown = 0;
+
+static void AppendToUartSimulationTextControl(BYTE b);
+
+static void SimulateIntCode(void);
+static char *MarkUsedVariable(char *name, DWORD flag);
+
+//-----------------------------------------------------------------------------
+// Query the state of a single-bit element (relay, digital in, digital out).
+// Looks in the SingleBitItems list; if an item is not present then it is
+// FALSE by default.
+//-----------------------------------------------------------------------------
+static BOOL SingleBitOn(char *name)
+{
+ int i;
+ for(i = 0; i < SingleBitItemsCount; i++) {
+ if(strcmp(SingleBitItems[i].name, name)==0) {
+ return SingleBitItems[i].powered;
+ }
+ }
+ return FALSE;
+}
+
+//-----------------------------------------------------------------------------
+// Set the state of a single-bit item. Adds it to the list if it is not there
+// already.
+//-----------------------------------------------------------------------------
+static void SetSingleBit(char *name, BOOL state)
+{
+ int i;
+ for(i = 0; i < SingleBitItemsCount; i++) {
+ if(strcmp(SingleBitItems[i].name, name)==0) {
+ SingleBitItems[i].powered = state;
+ return;
+ }
+ }
+ if(i < MAX_IO) {
+ strcpy(SingleBitItems[i].name, name);
+ SingleBitItems[i].powered = state;
+ SingleBitItemsCount++;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Count a timer up (i.e. increment its associated count by 1). Must already
+// exist in the table.
+//-----------------------------------------------------------------------------
+static void IncrementVariable(char *name)
+{
+ int i;
+ for(i = 0; i < VariablesCount; i++) {
+ if(strcmp(Variables[i].name, name)==0) {
+ (Variables[i].val)++;
+ return;
+ }
+ }
+ oops();
+}
+
+//-----------------------------------------------------------------------------
+// Set a variable to a value.
+//-----------------------------------------------------------------------------
+static void SetSimulationVariable(char *name, SWORD val)
+{
+ int i;
+ for(i = 0; i < VariablesCount; i++) {
+ if(strcmp(Variables[i].name, name)==0) {
+ Variables[i].val = val;
+ return;
+ }
+ }
+ MarkUsedVariable(name, VAR_FLAG_OTHERWISE_FORGOTTEN);
+ SetSimulationVariable(name, val);
+}
+
+//-----------------------------------------------------------------------------
+// Read a variable's value.
+//-----------------------------------------------------------------------------
+SWORD GetSimulationVariable(char *name)
+{
+ int i;
+ for(i = 0; i < VariablesCount; i++) {
+ if(strcmp(Variables[i].name, name)==0) {
+ return Variables[i].val;
+ }
+ }
+ MarkUsedVariable(name, VAR_FLAG_OTHERWISE_FORGOTTEN);
+ return GetSimulationVariable(name);
+}
+
+//-----------------------------------------------------------------------------
+// Set the shadow copy of a variable associated with a READ ADC operation. This
+// will get committed to the real copy when the rung-in condition to the
+// READ ADC is true.
+//-----------------------------------------------------------------------------
+void SetAdcShadow(char *name, SWORD val)
+{
+ int i;
+ for(i = 0; i < AdcShadowsCount; i++) {
+ if(strcmp(AdcShadows[i].name, name)==0) {
+ AdcShadows[i].val = val;
+ return;
+ }
+ }
+ strcpy(AdcShadows[i].name, name);
+ AdcShadows[i].val = val;
+ AdcShadowsCount++;
+}
+
+//-----------------------------------------------------------------------------
+// Return the shadow value of a variable associated with a READ ADC. This is
+// what gets copied into the real variable when an ADC read is simulated.
+//-----------------------------------------------------------------------------
+SWORD GetAdcShadow(char *name)
+{
+ int i;
+ for(i = 0; i < AdcShadowsCount; i++) {
+ if(strcmp(AdcShadows[i].name, name)==0) {
+ return AdcShadows[i].val;
+ }
+ }
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+// Mark how a variable is used; a series of flags that we can OR together,
+// then we can check to make sure that only valid combinations have been used
+// (e.g. just a TON, an RTO with its reset, etc.). Returns NULL for success,
+// else an error string.
+//-----------------------------------------------------------------------------
+static char *MarkUsedVariable(char *name, DWORD flag)
+{
+ int i;
+ for(i = 0; i < VariablesCount; i++) {
+ if(strcmp(Variables[i].name, name)==0) {
+ break;
+ }
+ }
+ if(i >= MAX_IO) return "";
+
+ if(i == VariablesCount) {
+ strcpy(Variables[i].name, name);
+ Variables[i].usedFlags = 0;
+ Variables[i].val = 0;
+ VariablesCount++;
+ }
+
+ switch(flag) {
+ case VAR_FLAG_TOF:
+ if(Variables[i].usedFlags != 0)
+ return _("TOF: variable cannot be used elsewhere");
+ break;
+
+ case VAR_FLAG_TON:
+ if(Variables[i].usedFlags != 0)
+ return _("TON: variable cannot be used elsewhere");
+ break;
+
+ case VAR_FLAG_RTO:
+ if(Variables[i].usedFlags & ~VAR_FLAG_RES)
+ return _("RTO: variable can only be used for RES elsewhere");
+ break;
+
+ case VAR_FLAG_CTU:
+ case VAR_FLAG_CTD:
+ case VAR_FLAG_CTC:
+ case VAR_FLAG_RES:
+ case VAR_FLAG_ANY:
+ break;
+
+ case VAR_FLAG_OTHERWISE_FORGOTTEN:
+ if(name[0] != '$') {
+ Error(_("Variable '%s' not assigned to, e.g. with a "
+ "MOV statement, an ADD statement, etc.\r\n\r\n"
+ "This is probably a programming error; now it "
+ "will always be zero."), name);
+ }
+ break;
+
+ default:
+ oops();
+ }
+
+ Variables[i].usedFlags |= flag;
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Check for duplicate uses of a single variable. For example, there should
+// not be two TONs with the same name. On the other hand, it would be okay
+// to have an RTO with the same name as its reset; in fact, verify that
+// there must be a reset for each RTO.
+//-----------------------------------------------------------------------------
+static void MarkWithCheck(char *name, int flag)
+{
+ char *s = MarkUsedVariable(name, flag);
+ if(s) {
+ Error(_("Variable for '%s' incorrectly assigned: %s."), name, s);
+ }
+}
+
+static void CheckVariableNamesCircuit(int which, void *elem)
+{
+ ElemLeaf *l = (ElemLeaf *)elem;
+ char *name = NULL;
+ DWORD flag;
+
+ switch(which) {
+ case ELEM_SERIES_SUBCKT: {
+ int i;
+ ElemSubcktSeries *s = (ElemSubcktSeries *)elem;
+ for(i = 0; i < s->count; i++) {
+ CheckVariableNamesCircuit(s->contents[i].which,
+ s->contents[i].d.any);
+ }
+ break;
+ }
+
+ case ELEM_PARALLEL_SUBCKT: {
+ int i;
+ ElemSubcktParallel *p = (ElemSubcktParallel *)elem;
+ for(i = 0; i < p->count; i++) {
+ CheckVariableNamesCircuit(p->contents[i].which,
+ p->contents[i].d.any);
+ }
+ break;
+ }
+
+ case ELEM_RTO:
+ case ELEM_TOF:
+ case ELEM_TON:
+ if(which == ELEM_RTO)
+ flag = VAR_FLAG_RTO;
+ else if(which == ELEM_TOF)
+ flag = VAR_FLAG_TOF;
+ else if(which == ELEM_TON)
+ flag = VAR_FLAG_TON;
+ else oops();
+
+ MarkWithCheck(l->d.timer.name, flag);
+
+ break;
+
+ case ELEM_CTU:
+ case ELEM_CTD:
+ case ELEM_CTC:
+ if(which == ELEM_CTU)
+ flag = VAR_FLAG_CTU;
+ else if(which == ELEM_CTD)
+ flag = VAR_FLAG_CTD;
+ else if(which == ELEM_CTC)
+ flag = VAR_FLAG_CTC;
+ else oops();
+
+ MarkWithCheck(l->d.counter.name, flag);
+
+ break;
+
+ case ELEM_RES:
+ MarkWithCheck(l->d.reset.name, VAR_FLAG_RES);
+ break;
+
+ case ELEM_MOVE:
+ MarkWithCheck(l->d.move.dest, VAR_FLAG_ANY);
+ break;
+
+ case ELEM_LOOK_UP_TABLE:
+ MarkWithCheck(l->d.lookUpTable.dest, VAR_FLAG_ANY);
+ break;
+
+ case ELEM_PIECEWISE_LINEAR:
+ MarkWithCheck(l->d.piecewiseLinear.dest, VAR_FLAG_ANY);
+ break;
+
+ case ELEM_READ_ADC:
+ MarkWithCheck(l->d.readAdc.name, VAR_FLAG_ANY);
+ break;
+
+ case ELEM_ADD:
+ case ELEM_SUB:
+ case ELEM_MUL:
+ case ELEM_DIV:
+ MarkWithCheck(l->d.math.dest, VAR_FLAG_ANY);
+ break;
+
+ case ELEM_UART_RECV:
+ MarkWithCheck(l->d.uart.name, VAR_FLAG_ANY);
+ break;
+
+ case ELEM_SHIFT_REGISTER: {
+ int i;
+ for(i = 1; i < l->d.shiftRegister.stages; i++) {
+ char str[MAX_NAME_LEN+10];
+ sprintf(str, "%s%d", l->d.shiftRegister.name, i);
+ MarkWithCheck(str, VAR_FLAG_ANY);
+ }
+ break;
+ }
+
+ case ELEM_PERSIST:
+ case ELEM_FORMATTED_STRING:
+ case ELEM_SET_PWM:
+ case ELEM_MASTER_RELAY:
+ case ELEM_UART_SEND:
+ case ELEM_PLACEHOLDER:
+ case ELEM_COMMENT:
+ case ELEM_OPEN:
+ case ELEM_SHORT:
+ case ELEM_COIL:
+ case ELEM_CONTACTS:
+ case ELEM_ONE_SHOT_RISING:
+ case ELEM_ONE_SHOT_FALLING:
+ case ELEM_EQU:
+ case ELEM_NEQ:
+ case ELEM_GRT:
+ case ELEM_GEQ:
+ case ELEM_LES:
+ case ELEM_LEQ:
+ break;
+
+ default:
+ oops();
+ }
+}
+
+static void CheckVariableNames(void)
+{
+ int i;
+ for(i = 0; i < Prog.numRungs; i++) {
+ CheckVariableNamesCircuit(ELEM_SERIES_SUBCKT, Prog.rungs[i]);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// The IF condition is true. Execute the body, up until the ELSE or the
+// END IF, and then skip the ELSE if it is present. Called with PC on the
+// IF, returns with PC on the END IF.
+//-----------------------------------------------------------------------------
+static void IfConditionTrue(void)
+{
+ IntPc++;
+ // now PC is on the first statement of the IF body
+ SimulateIntCode();
+ // now PC is on the ELSE or the END IF
+ if(IntCode[IntPc].op == INT_ELSE) {
+ int nesting = 1;
+ for(; ; IntPc++) {
+ if(IntPc >= IntCodeLen) oops();
+
+ if(IntCode[IntPc].op == INT_END_IF) {
+ nesting--;
+ } else if(INT_IF_GROUP(IntCode[IntPc].op)) {
+ nesting++;
+ }
+ if(nesting == 0) break;
+ }
+ } else if(IntCode[IntPc].op == INT_END_IF) {
+ return;
+ } else {
+ oops();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// The IF condition is false. Skip the body, up until the ELSE or the END
+// IF, and then execute the ELSE if it is present. Called with PC on the IF,
+// returns with PC on the END IF.
+//-----------------------------------------------------------------------------
+static void IfConditionFalse(void)
+{
+ int nesting = 0;
+ for(; ; IntPc++) {
+ if(IntPc >= IntCodeLen) oops();
+
+ if(IntCode[IntPc].op == INT_END_IF) {
+ nesting--;
+ } else if(INT_IF_GROUP(IntCode[IntPc].op)) {
+ nesting++;
+ } else if(IntCode[IntPc].op == INT_ELSE && nesting == 1) {
+ break;
+ }
+ if(nesting == 0) break;
+ }
+
+ // now PC is on the ELSE or the END IF
+ if(IntCode[IntPc].op == INT_ELSE) {
+ IntPc++;
+ SimulateIntCode();
+ } else if(IntCode[IntPc].op == INT_END_IF) {
+ return;
+ } else {
+ oops();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Evaluate a circuit, calling ourselves recursively to evaluate if/else
+// constructs. Updates the on/off state of all the leaf elements in our
+// internal tables. Returns when it reaches an end if or an else construct,
+// or at the end of the program.
+//-----------------------------------------------------------------------------
+static void SimulateIntCode(void)
+{
+ for(; IntPc < IntCodeLen; IntPc++) {
+ IntOp *a = &IntCode[IntPc];
+ switch(a->op) {
+ case INT_SIMULATE_NODE_STATE:
+ if(*(a->poweredAfter) != SingleBitOn(a->name1))
+ NeedRedraw = TRUE;
+ *(a->poweredAfter) = SingleBitOn(a->name1);
+ break;
+
+ case INT_SET_BIT:
+ SetSingleBit(a->name1, TRUE);
+ break;
+
+ case INT_CLEAR_BIT:
+ SetSingleBit(a->name1, FALSE);
+ break;
+
+ case INT_COPY_BIT_TO_BIT:
+ SetSingleBit(a->name1, SingleBitOn(a->name2));
+ break;
+
+ case INT_SET_VARIABLE_TO_LITERAL:
+ if(GetSimulationVariable(a->name1) !=
+ a->literal && a->name1[0] != '$')
+ {
+ NeedRedraw = TRUE;
+ }
+ SetSimulationVariable(a->name1, a->literal);
+ break;
+
+ case INT_SET_VARIABLE_TO_VARIABLE:
+ if(GetSimulationVariable(a->name1) !=
+ GetSimulationVariable(a->name2))
+ {
+ NeedRedraw = TRUE;
+ }
+ SetSimulationVariable(a->name1,
+ GetSimulationVariable(a->name2));
+ break;
+
+ case INT_INCREMENT_VARIABLE:
+ IncrementVariable(a->name1);
+ break;
+
+ {
+ SWORD v;
+ case INT_SET_VARIABLE_ADD:
+ v = GetSimulationVariable(a->name2) +
+ GetSimulationVariable(a->name3);
+ goto math;
+ case INT_SET_VARIABLE_SUBTRACT:
+ v = GetSimulationVariable(a->name2) -
+ GetSimulationVariable(a->name3);
+ goto math;
+ case INT_SET_VARIABLE_MULTIPLY:
+ v = GetSimulationVariable(a->name2) *
+ GetSimulationVariable(a->name3);
+ goto math;
+ case INT_SET_VARIABLE_DIVIDE:
+ if(GetSimulationVariable(a->name3) != 0) {
+ v = GetSimulationVariable(a->name2) /
+ GetSimulationVariable(a->name3);
+ } else {
+ v = 0;
+ Error(_("Division by zero; halting simulation"));
+ StopSimulation();
+ }
+ goto math;
+math:
+ if(GetSimulationVariable(a->name1) != v) {
+ NeedRedraw = TRUE;
+ SetSimulationVariable(a->name1, v);
+ }
+ break;
+ }
+
+#define IF_BODY \
+ { \
+ IfConditionTrue(); \
+ } else { \
+ IfConditionFalse(); \
+ }
+ case INT_IF_BIT_SET:
+ if(SingleBitOn(a->name1))
+ IF_BODY
+ break;
+
+ case INT_IF_BIT_CLEAR:
+ if(!SingleBitOn(a->name1))
+ IF_BODY
+ break;
+
+ case INT_IF_VARIABLE_LES_LITERAL:
+ if(GetSimulationVariable(a->name1) < a->literal)
+ IF_BODY
+ break;
+
+ case INT_IF_VARIABLE_EQUALS_VARIABLE:
+ if(GetSimulationVariable(a->name1) ==
+ GetSimulationVariable(a->name2))
+ IF_BODY
+ break;
+
+ case INT_IF_VARIABLE_GRT_VARIABLE:
+ if(GetSimulationVariable(a->name1) >
+ GetSimulationVariable(a->name2))
+ IF_BODY
+ break;
+
+ case INT_SET_PWM:
+ // Dummy call will cause a warning if no one ever assigned
+ // to that variable.
+ (void)GetSimulationVariable(a->name1);
+ break;
+
+ // Don't try to simulate the EEPROM stuff: just hold the EEPROM
+ // busy all the time, so that the program never does anything
+ // with it.
+ case INT_EEPROM_BUSY_CHECK:
+ SetSingleBit(a->name1, TRUE);
+ break;
+
+ case INT_EEPROM_READ:
+ case INT_EEPROM_WRITE:
+ oops();
+ break;
+
+ case INT_READ_ADC:
+ // Keep the shadow copies of the ADC variables because in
+ // the real device they will not be updated until an actual
+ // read is performed, which occurs only for a true rung-in
+ // condition there.
+ SetSimulationVariable(a->name1, GetAdcShadow(a->name1));
+ break;
+
+ case INT_UART_SEND:
+ if(SingleBitOn(a->name2) && (SimulateUartTxCountdown == 0)) {
+ SimulateUartTxCountdown = 2;
+ AppendToUartSimulationTextControl(
+ (BYTE)GetSimulationVariable(a->name1));
+ }
+ if(SimulateUartTxCountdown == 0) {
+ SetSingleBit(a->name2, FALSE);
+ } else {
+ SetSingleBit(a->name2, TRUE);
+ }
+ break;
+
+ case INT_UART_RECV:
+ if(QueuedUartCharacter >= 0) {
+ SetSingleBit(a->name2, TRUE);
+ SetSimulationVariable(a->name1, (SWORD)QueuedUartCharacter);
+ QueuedUartCharacter = -1;
+ } else {
+ SetSingleBit(a->name2, FALSE);
+ }
+ break;
+
+ case INT_END_IF:
+ case INT_ELSE:
+ return;
+
+ case INT_COMMENT:
+ break;
+
+ default:
+ oops();
+ break;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Called by the Windows timer that triggers cycles when we are running
+// in real time.
+//-----------------------------------------------------------------------------
+BOOL PlcCycleTimer(BOOL kill = FALSE)
+{
+ for(int i = 0; i < CyclesPerTimerTick; i++) {
+ SimulateOneCycle(FALSE);
+ }
+
+ return !kill;
+}
+
+//-----------------------------------------------------------------------------
+// Simulate one cycle of the PLC. Update everything, and keep track of whether
+// any outputs have changed. If so, force a screen refresh. If requested do
+// a screen refresh regardless.
+//-----------------------------------------------------------------------------
+void SimulateOneCycle(BOOL forceRefresh)
+{
+ // When there is an error message up, the modal dialog makes its own
+ // event loop, and there is risk that we would go recursive. So let
+ // us fix that. (Note that there are no concurrency issues; we really
+ // would get called recursively, not just reentrantly.)
+ static BOOL Simulating = FALSE;
+
+ if(Simulating) return;
+ Simulating = TRUE;
+
+ NeedRedraw = FALSE;
+
+ if(SimulateUartTxCountdown > 0) {
+ SimulateUartTxCountdown--;
+ } else {
+ SimulateUartTxCountdown = 0;
+ }
+
+ IntPc = 0;
+ SimulateIntCode();
+
+ if(NeedRedraw || SimulateRedrawAfterNextCycle || forceRefresh) {
+ InvalidateRect(DrawWindow, NULL, FALSE);
+ RefreshControlsToSettings();
+ gtk_widget_queue_draw(DrawWindow);
+ // ListView_RedrawItems(IoList, 0, Prog.io.count - 1);
+ }
+
+ SimulateRedrawAfterNextCycle = FALSE;
+ if(NeedRedraw) SimulateRedrawAfterNextCycle = TRUE;
+
+ Simulating = FALSE;
+}
+
+//-----------------------------------------------------------------------------
+// Start the timer that we use to trigger PLC cycles in approximately real
+// time. Independently of the given cycle time, just go at 40 Hz, since that
+// is about as fast as anyone could follow by eye. Faster timers will just
+// go instantly.
+//-----------------------------------------------------------------------------
+void StartSimulationTimer(void)
+{
+ int p = Prog.cycleTime/1000;
+ if(p < 5) {
+ SetTimer(MainWindow, TIMER_SIMULATE, 10, PlcCycleTimer);
+ CyclesPerTimerTick = 10000 / Prog.cycleTime;
+ } else {
+ SetTimer(MainWindow, TIMER_SIMULATE, p, PlcCycleTimer);
+ CyclesPerTimerTick = 1;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Clear out all the parameters relating to the previous simulation.
+//-----------------------------------------------------------------------------
+void ClearSimulationData(void)
+{
+ VariablesCount = 0;
+ SingleBitItemsCount = 0;
+ AdcShadowsCount = 0;
+ QueuedUartCharacter = -1;
+ SimulateUartTxCountdown = 0;
+
+ CheckVariableNames();
+
+ SimulateRedrawAfterNextCycle = TRUE;
+
+ if(!GenerateIntermediateCode()) {
+ ToggleSimulationMode();
+ return;
+ }
+
+ SimulateOneCycle(TRUE);
+}
+
+//-----------------------------------------------------------------------------
+// Provide a description for an item (Xcontacts, Ycoil, Rrelay, Ttimer,
+// or other) in the I/O list.
+//-----------------------------------------------------------------------------
+void DescribeForIoList(char *name, char *out)
+{
+ switch(name[0]) {
+ case 'R':
+ case 'X':
+ case 'Y':
+ sprintf(out, "%d", SingleBitOn(name));
+ break;
+
+ case 'T': {
+ double dtms = GetSimulationVariable(name) *
+ (Prog.cycleTime / 1000.0);
+ if(dtms < 1000) {
+ sprintf(out, "%.2f ms", dtms);
+ } else {
+ sprintf(out, "%.3f s", dtms / 1000);
+ }
+ break;
+ }
+ default: {
+ SWORD v = GetSimulationVariable(name);
+ sprintf(out, "%hd (0x%04hx)", v, v);
+ break;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Toggle the state of a contact input; for simulation purposes, so that we
+// can set the input state of the program.
+//-----------------------------------------------------------------------------
+void SimulationToggleContact(char *name)
+{
+ SetSingleBit(name, !SingleBitOn(name));
+ // ListView_RedrawItems(IoList, 0, Prog.io.count - 1);
+}
+
+//-----------------------------------------------------------------------------
+// Dialog proc for the popup that lets you interact with the UART stuff.
+//-----------------------------------------------------------------------------
+// static LRESULT CALLBACK UartSimulationProc(HWND hwnd, UINT msg,
+// WPARAM wParam, LPARAM lParam)
+// {
+// switch (msg) {
+// case WM_DESTROY:
+// DestroyUartSimulationWindow();
+// break;
+
+// case WM_CLOSE:
+// break;
+
+// case WM_SIZE:
+// MoveWindow(UartSimulationTextControl, 0, 0, LOWORD(lParam),
+// HIWORD(lParam), TRUE);
+// break;
+
+// case WM_ACTIVATE:
+// if(wParam != WA_INACTIVE) {
+// SetFocus(UartSimulationTextControl);
+// }
+// break;
+
+// default:
+// return DefWindowProc(hwnd, msg, wParam, lParam);
+// }
+// return 1;
+// }
+
+//-----------------------------------------------------------------------------
+// Intercept WM_CHAR messages that to the terminal simulation window so that
+// we can redirect them to the PLC program.
+//
+// Ported: Read and write text fron the text view widget.
+//-----------------------------------------------------------------------------
+static void UartSimulationTextProc(HWID hwid, UINT umsg, char *text, UINT uszbuf)
+{
+ switch(umsg)
+ {
+ case WM_SETTEXT:
+ {
+ GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(hwid));
+ gtk_text_buffer_set_text (buffer, text, -1);
+ gtk_text_view_set_buffer (GTK_TEXT_VIEW(hwid), buffer);
+
+ GtkTextIter end;
+ gtk_text_buffer_get_end_iter (buffer, &end);
+ gtk_text_view_scroll_to_iter (GTK_TEXT_VIEW(hwid), &end, 0.2, FALSE, 1, 1);
+ break;
+ }
+ case WM_SETTEXT_END:
+ {
+ GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(hwid));
+ gtk_text_buffer_insert_at_cursor (buffer, text, -1);
+ gtk_text_view_set_buffer (GTK_TEXT_VIEW(hwid), buffer);
+
+ GtkTextIter end;
+ gtk_text_buffer_get_end_iter (buffer, &end);
+ gtk_text_view_scroll_to_iter (GTK_TEXT_VIEW(hwid), &end, 0.2, FALSE, 1, 1);
+ break;
+ }
+ case WM_GETTEXT:
+ {
+ GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(hwid));
+ GtkTextIter start, end;
+ gtk_text_buffer_get_start_iter (buffer, &start);
+ gtk_text_buffer_get_end_iter (buffer, &end);
+
+ char *txtBuf = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
+
+ strcpy(text, txtBuf);
+ strcat(text, "\0");
+ g_free(txtBuf);
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Pop up the UART simulation window; like a terminal window where the
+// characters that you type go into UART RECV instruction and whatever
+// the program puts into UART SEND shows up as text.
+//-----------------------------------------------------------------------------
+void ShowUartSimulationWindow(void)
+{
+ DWORD TerminalX = 200, TerminalY = 200, TerminalW = 300, TerminalH = 150;
+
+ ThawDWORD(TerminalX);
+ ThawDWORD(TerminalY);
+ ThawDWORD(TerminalW);
+ ThawDWORD(TerminalH);
+
+ if(TerminalW > 800) TerminalW = 100;
+ if(TerminalH > 800) TerminalH = 100;
+
+ UartSimulationWindow = CreateWindowClient(GTK_WINDOW_TOPLEVEL, GDK_WINDOW_TYPE_HINT_NORMAL,
+ "UART Simulation (Terminal)", TerminalX, TerminalY, TerminalW, TerminalH, NULL);
+ /// remove close button
+ gtk_window_set_deletable (GTK_WINDOW(UartSimulationWindow), FALSE);
+
+ UartSimulationTextControl = gtk_text_view_new();
+
+ gtk_widget_override_font(GTK_WIDGET(UartSimulationTextControl), pango_font_description_from_string("Lucida Console"));
+
+ /// Add text view into a scrolled window to enable scrolling functionality
+ HWID TextViewScroll = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (TextViewScroll),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_widget_set_hexpand(GTK_WIDGET(TextViewScroll), TRUE);
+ gtk_widget_set_vexpand(GTK_WIDGET(TextViewScroll), TRUE);
+
+ gtk_container_add (GTK_CONTAINER(TextViewScroll), UartSimulationTextControl);
+ gtk_container_add (GTK_CONTAINER(UartSimulationWindow), TextViewScroll);
+
+ gtk_widget_show_all(UartSimulationWindow);
+
+ gtk_window_set_keep_above (GTK_WINDOW(MainWindow), TRUE);
+ gtk_window_set_focus_visible (GTK_WINDOW(MainWindow), TRUE);
+ gtk_window_set_keep_above (GTK_WINDOW(MainWindow), FALSE);
+}
+
+//-----------------------------------------------------------------------------
+// Get rid of the UART simulation terminal-type window.
+//-----------------------------------------------------------------------------
+void DestroyUartSimulationWindow(void)
+{
+ // Try not to destroy the window if it is already destroyed; that is
+ // not for the sake of the window, but so that we don't trash the
+ // stored position.
+ if(UartSimulationWindow == NULL) return;
+
+ DWORD TerminalX, TerminalY, TerminalW, TerminalH;
+ RECT r;
+
+ GetClientRect(UartSimulationWindow, &r);
+ TerminalW = r.right - r.left;
+ TerminalH = r.bottom - r.top;
+
+ GetWindowRect(UartSimulationWindow, &r);
+ TerminalX = r.left;
+ TerminalY = r.top;
+
+ FreezeDWORD(TerminalX);
+ FreezeDWORD(TerminalY);
+ FreezeDWORD(TerminalW);
+ FreezeDWORD(TerminalH);
+
+ DestroyWindow(UartSimulationWindow);
+ ProgramChanged();
+ UartSimulationWindow = NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Append a received character to the terminal buffer.
+//-----------------------------------------------------------------------------
+static void AppendToUartSimulationTextControl(BYTE b)
+{
+ char append[5];
+
+ if((isalnum(b) || strchr("[]{};':\",.<>/?`~ !@#$%^&*()-=_+|", b) ||
+ b == '\r' || b == '\n') && b != '\0')
+ {
+ append[0] = b;
+ append[1] = '\0';
+ } else {
+ sprintf(append, "\\x%02x", b);
+ }
+
+#define MAX_SCROLLBACK 256
+ char buf[MAX_SCROLLBACK];
+
+ UartSimulationTextProc(UartSimulationTextControl, WM_GETTEXT, buf, strlen(buf));
+
+ int overBy = (strlen(buf) + strlen(append) + 1) - sizeof(buf);
+ if(overBy > 0) {
+ memmove(buf, buf + overBy, strlen(buf));
+ }
+ strcat(buf, append);
+
+ UartSimulationTextProc(UartSimulationTextControl, WM_SETTEXT, buf, strlen(buf));
+}