diff options
author | akshay-c | 2019-01-30 12:23:44 +0530 |
---|---|---|
committer | akshay-c | 2019-01-30 12:23:44 +0530 |
commit | 4196481f74afb84e5cc59cdf00c06c1ca1becab7 (patch) | |
tree | b531deb0466897691f08f9076b7012592f026664 /ldmicro/simulate.cpp | |
download | LDmicroQt-4196481f74afb84e5cc59cdf00c06c1ca1becab7.tar.gz LDmicroQt-4196481f74afb84e5cc59cdf00c06c1ca1becab7.tar.bz2 LDmicroQt-4196481f74afb84e5cc59cdf00c06c1ca1becab7.zip |
First commit
Diffstat (limited to 'ldmicro/simulate.cpp')
-rw-r--r-- | ldmicro/simulate.cpp | 992 |
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)); +} |