Merge branch 'telnet'

This commit is contained in:
hneemann 2021-05-12 10:21:40 +02:00
commit b3332e0ae3
12 changed files with 721 additions and 20 deletions

View File

@ -0,0 +1,162 @@
<?xml version="1.0" encoding="utf-8"?>
<circuit>
<version>1</version>
<attributes/>
<visualElements>
<visualElement>
<elementName>Telnet</elementName>
<elementAttributes>
<entry>
<string>port</string>
<int>2323</int>
</entry>
</elementAttributes>
<pos x="400" y="140"/>
</visualElement>
<visualElement>
<elementName>Terminal</elementName>
<elementAttributes/>
<pos x="520" y="140"/>
</visualElement>
<visualElement>
<elementName>Keyboard</elementName>
<elementAttributes/>
<pos x="240" y="80"/>
</visualElement>
<visualElement>
<elementName>Clock</elementName>
<elementAttributes>
<entry>
<string>runRealTime</string>
<boolean>true</boolean>
</entry>
<entry>
<string>Frequency</string>
<int>20</int>
</entry>
</elementAttributes>
<pos x="180" y="160"/>
</visualElement>
<visualElement>
<elementName>Splitter</elementName>
<elementAttributes>
<entry>
<string>Input Splitting</string>
<string>16</string>
</entry>
</elementAttributes>
<pos x="340" y="80"/>
</visualElement>
</visualElements>
<wires>
<wire>
<p1 x="460" y="160"/>
<p2 x="480" y="160"/>
</wire>
<wire>
<p1 x="500" y="160"/>
<p2 x="520" y="160"/>
</wire>
<wire>
<p1 x="180" y="160"/>
<p2 x="200" y="160"/>
</wire>
<wire>
<p1 x="200" y="160"/>
<p2 x="400" y="160"/>
</wire>
<wire>
<p1 x="380" y="240"/>
<p2 x="480" y="240"/>
</wire>
<wire>
<p1 x="200" y="80"/>
<p2 x="240" y="80"/>
</wire>
<wire>
<p1 x="300" y="80"/>
<p2 x="340" y="80"/>
</wire>
<wire>
<p1 x="360" y="80"/>
<p2 x="380" y="80"/>
</wire>
<wire>
<p1 x="480" y="180"/>
<p2 x="520" y="180"/>
</wire>
<wire>
<p1 x="320" y="180"/>
<p2 x="400" y="180"/>
</wire>
<wire>
<p1 x="300" y="100"/>
<p2 x="320" y="100"/>
</wire>
<wire>
<p1 x="220" y="100"/>
<p2 x="240" y="100"/>
</wire>
<wire>
<p1 x="200" y="280"/>
<p2 x="500" y="280"/>
</wire>
<wire>
<p1 x="380" y="200"/>
<p2 x="400" y="200"/>
</wire>
<wire>
<p1 x="460" y="140"/>
<p2 x="520" y="140"/>
</wire>
<wire>
<p1 x="220" y="140"/>
<p2 x="320" y="140"/>
</wire>
<wire>
<p1 x="380" y="140"/>
<p2 x="400" y="140"/>
</wire>
<wire>
<p1 x="480" y="160"/>
<p2 x="480" y="180"/>
</wire>
<wire>
<p1 x="480" y="180"/>
<p2 x="480" y="240"/>
</wire>
<wire>
<p1 x="320" y="100"/>
<p2 x="320" y="140"/>
</wire>
<wire>
<p1 x="320" y="140"/>
<p2 x="320" y="180"/>
</wire>
<wire>
<p1 x="500" y="160"/>
<p2 x="500" y="280"/>
</wire>
<wire>
<p1 x="200" y="80"/>
<p2 x="200" y="160"/>
</wire>
<wire>
<p1 x="200" y="160"/>
<p2 x="200" y="280"/>
</wire>
<wire>
<p1 x="220" y="100"/>
<p2 x="220" y="140"/>
</wire>
<wire>
<p1 x="380" y="200"/>
<p2 x="380" y="240"/>
</wire>
<wire>
<p1 x="380" y="80"/>
<p2 x="380" y="140"/>
</wire>
</wires>
<measurementOrdering/>
</circuit>

View File

@ -932,5 +932,17 @@ public final class Keys {
public static final Key.KeyEnum<ScopeTrigger.Trigger> TRIGGER =
new Key.KeyEnum<>("trigger", ScopeTrigger.Trigger.both, ScopeTrigger.Trigger.values());
/**
* Selects the telnet port
*/
public static final Key.KeyInteger PORT =
new Key.KeyInteger("port", 23)
.setMin(1)
.setMax((1 << 16) - 1);
/**
* Telnet escape
*/
public static final Key<Boolean> TELNET_ESCAPE =
new Key<>("telnetEscape", true).allowGroupEdit();
}

View File

@ -0,0 +1,84 @@
/*
* Copyright (c) 2021 Helmut Neemann.
* Use of this source code is governed by the GPL v3 license
* that can be found in the LICENSE file.
*/
package de.neemann.digital.core.io.telnet;
/**
* A simple thread save byte queue.
*/
public class ByteBuffer {
private final byte[] data;
private final int size;
private int inBuffer;
private int newest;
private int oldest;
/**
* Creates a new instance
*
* @param size the size of the buffer
*/
public ByteBuffer(int size) {
data = new byte[size];
this.size = size;
}
/**
* Adds a byte at the top of the buffer
*
* @param value the byte value
*/
synchronized public void put(byte value) {
if (inBuffer < size) {
data[newest] = value;
newest = inc(newest);
inBuffer++;
}
}
/**
* @return the byte at the tail of the buffer
*/
synchronized public byte peek() {
if (inBuffer > 0) {
return data[oldest];
} else
return -1;
}
/**
* deletes a byte from the tail of the buffer
*/
synchronized public void delete() {
if (inBuffer > 0) {
oldest = inc(oldest);
inBuffer--;
}
}
/**
* deletes all buffered data
*/
synchronized public void deleteAll() {
oldest = 0;
newest = 0;
inBuffer = 0;
}
/**
* @return true if there is data available
*/
synchronized public boolean hasData() {
return inBuffer > 0;
}
private int inc(int n) {
n++;
if (n >= size)
n = 0;
return n;
}
}

View File

@ -0,0 +1,179 @@
/*
* Copyright (c) 2021 Helmut Neemann.
* Use of this source code is governed by the GPL v3 license
* that can be found in the LICENSE file.
*/
package de.neemann.digital.core.io.telnet;
import de.neemann.digital.core.SyncAccess;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* The telnet server
*/
public class Server {
private final ServerSocket serverSocket;
private final ByteBuffer buffer;
private final ServerThread serverThread;
private boolean telnetEscape;
private ClientThread client;
private Telnet telnet;
private SyncAccess syncAccess;
Server(int port) throws IOException {
buffer = new ByteBuffer(1024);
serverSocket = new ServerSocket(port);
serverThread = new ServerThread();
serverThread.start();
}
void send(int value) {
if (client != null)
client.send(value);
}
int getData() {
return buffer.peek();
}
void deleteOldest() {
buffer.delete();
}
void deleteAll() {
buffer.deleteAll();
}
/**
* Connects the server with the telnet node
*
* @param telnet the telnet node
* @param syncAccess used to access the model
*/
public void setTelnetNode(Telnet telnet, SyncAccess syncAccess) {
this.telnet = telnet;
this.syncAccess = syncAccess;
}
boolean hasData() {
return buffer.hasData();
}
private void setClient(ClientThread client) {
this.client = client;
}
void setTelnetEscape(boolean telnetEscape) {
this.telnetEscape = telnetEscape;
}
private void dataReceived(int data) {
if (syncAccess != null)
syncAccess.modify(() -> {
buffer.put((byte) data);
telnet.hasChanged();
});
}
boolean isDead() {
return !serverThread.isAlive();
}
private final class ServerThread extends Thread {
private ServerThread() {
setDaemon(true);
}
@Override
public void run() {
try {
while (true) {
Socket client = serverSocket.accept();
ClientThread cl = new ClientThread(client, Server.this);
cl.start();
setClient(cl);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
private static final class ClientThread extends Thread {
private static final int ECHO = 1;
private static final int SGA = 3;
private static final int WILL = 251;
private static final int WONT = 252;
private static final int DO = 253;
private static final int DONT = 254;
private static final int IAC = 255;
private final InputStream in;
private final OutputStream out;
private final Socket client;
private final Server server;
private ClientThread(Socket client, Server server) throws IOException {
setDaemon(true);
in = client.getInputStream();
out = client.getOutputStream();
if (server.telnetEscape) {
out.write(IAC);
out.write(WILL);
out.write(SGA);
out.write(IAC);
out.write(WILL);
out.write(ECHO);
out.flush();
}
this.client = client;
this.server = server;
}
@Override
public void run() {
try {
while (true) {
int data = in.read();
if (data < 0)
break;
if (data == IAC && server.telnetEscape) {
int command = in.read();
int option = in.read();
} else
server.dataReceived(data);
}
} catch (IOException e) {
e.printStackTrace();
}
try {
client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public void send(int value) {
try {
out.write(value);
out.flush();
} catch (IOException e) {
e.printStackTrace();
try {
client.close();
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright (c) 2021 Helmut Neemann.
* Use of this source code is governed by the GPL v3 license
* that can be found in the LICENSE file.
*/
package de.neemann.digital.core.io.telnet;
import java.io.IOException;
import java.util.HashMap;
/**
* Simple singleton to hold the server instances.
* Usage of this singleton allows the telnet client to stay connected
* also if the simulation is not running.
*/
public final class ServerHolder {
/**
* The singleton instance
*/
public static final ServerHolder INSTANCE = new ServerHolder();
private final HashMap<Integer, Server> serverMap;
private ServerHolder() {
serverMap = new HashMap<>();
}
/**
* Returns a server.
*
* @param port the port
* @return the server
* @throws IOException IOException
*/
public Server getServer(int port) throws IOException {
Server server = serverMap.get(port);
if (server == null || server.isDead()) {
server = new Server(port);
serverMap.put(port, server);
} else
server.deleteAll();
return server;
}
}

View File

@ -0,0 +1,109 @@
/*
* Copyright (c) 2021 Helmut Neemann.
* Use of this source code is governed by the GPL v3 license
* that can be found in the LICENSE file.
*/
package de.neemann.digital.core.io.telnet;
import de.neemann.digital.core.*;
import de.neemann.digital.core.element.Element;
import de.neemann.digital.core.element.ElementAttributes;
import de.neemann.digital.core.element.ElementTypeDescription;
import de.neemann.digital.core.element.Keys;
import de.neemann.digital.draw.elements.PinException;
import de.neemann.digital.lang.Lang;
import java.io.IOException;
import static de.neemann.digital.core.element.PinInfo.input;
/**
* The telnet node
*/
public class Telnet extends Node implements Element {
/**
* The telnet server description
*/
public static final ElementTypeDescription DESCRIPTION = new ElementTypeDescription(Telnet.class,
input("in"), input("C").setClock(), input("wr"), input("rd"))
.addAttribute(Keys.ROTATE)
.addAttribute(Keys.LABEL)
.addAttribute(Keys.TELNET_ESCAPE)
.addAttribute(Keys.PORT);
private final ObservableValue dataOut;
private final ObservableValue dataAvail;
private final int port;
private final boolean telnetEscape;
private ObservableValue dataIn;
private ObservableValue clockValue;
private ObservableValue writeEnable;
private ObservableValue readEnableValue;
private Server server;
private boolean lastClock;
private boolean readEnable;
/**
* Creates a new instance
*
* @param attributes The components attributes
*/
public Telnet(ElementAttributes attributes) {
dataOut = new ObservableValue("out", 8)
.setToHighZ()
.setPinDescription(DESCRIPTION);
dataAvail = new ObservableValue("av", 1)
.setPinDescription(DESCRIPTION);
port = attributes.get(Keys.PORT);
telnetEscape = attributes.get(Keys.TELNET_ESCAPE);
}
@Override
public void setInputs(ObservableValues inputs) throws NodeException {
dataIn = inputs.get(0).checkBits(8, this, 0);
clockValue = inputs.get(1).checkBits(1, this, 1).addObserverToValue(this);
writeEnable = inputs.get(2).checkBits(1, this, 2);
readEnableValue = inputs.get(3).checkBits(1, this, 3).addObserverToValue(this);
}
@Override
public void readInputs() throws NodeException {
boolean clock = clockValue.getBool();
readEnable = readEnableValue.getBool();
if (clock & !lastClock) {
if (writeEnable.getBool())
server.send((int) dataIn.getValue());
if (readEnable)
server.deleteOldest();
}
lastClock = clock;
}
@Override
public void writeOutputs() throws NodeException {
if (readEnable)
dataOut.setValue(server.getData());
else
dataOut.setToHighZ();
dataAvail.setBool(server.hasData());
}
@Override
public ObservableValues getOutputs() throws PinException {
return new ObservableValues(dataOut, dataAvail);
}
@Override
public void init(Model model) throws NodeException {
try {
server = ServerHolder.INSTANCE.getServer(port);
} catch (IOException e) {
throw new NodeException(Lang.get("err_couldNotCreateServer"), e);
}
server.setTelnetEscape(telnetEscape);
server.setTelnetNode(this, model);
}
}

View File

@ -0,0 +1,10 @@
/*
* Copyright (c) 2021 Helmut Neemann.
* Use of this source code is governed by the GPL v3 license
* that can be found in the LICENSE file.
*/
/**
* Implement's a telnet connection to the simulator
*/
package de.neemann.digital.core.io.telnet;

View File

@ -15,6 +15,7 @@ import de.neemann.digital.core.extern.External;
import de.neemann.digital.core.extern.ExternalFile;
import de.neemann.digital.core.flipflops.*;
import de.neemann.digital.core.io.*;
import de.neemann.digital.core.io.telnet.Telnet;
import de.neemann.digital.core.memory.*;
import de.neemann.digital.core.pld.DiodeBackward;
import de.neemann.digital.core.pld.DiodeForward;
@ -150,6 +151,7 @@ public class ElementLibrary implements Iterable<ElementLibrary.ElementContainer>
.add(new LibraryNode(Lang.get("lib_peripherals"))
.add(Keyboard.DESCRIPTION)
.add(Terminal.DESCRIPTION)
.add(Telnet.DESCRIPTION)
.add(VGA.DESCRIPTION)
.add(MIDI.DESCRIPTION)
)

View File

@ -280,6 +280,16 @@
</string>
<string name="elem_Terminal_pin_en">Ein High an diesem Eingang aktiviert den Takteingang.</string>
<string name="elem_Telnet">Telnet</string>
<string name="elem_Telnet_tt">Erlaubt eine Telnet-Verbindung zur Schaltung.
Es können per Telnet Zeichen empfangen und gesendet werden.</string>
<string name="elem_Telnet_pin_out">Datenausgabe</string>
<string name="elem_Telnet_pin_av">Gibt eine Eins aus, wenn Daten vorhanden sind.</string>
<string name="elem_Telnet_pin_in">Die zu sendenden Daten.</string>
<string name="elem_Telnet_pin_C">Takteingang.</string>
<string name="elem_Telnet_pin_wr">Wenn gesetzt, wird das Eingangsdatenbyte gesendet.</string>
<string name="elem_Telnet_pin_rd">Wenn gesetzt, wird ein empfangenes Byte ausgegeben.</string>
<string name="elem_VGA">VGA Bildschirm</string>
<string name="elem_VGA_short">VGA</string>
<string name="elem_VGA_tt">Analysiert die eingehenden Video-Signale und zeigt die entsprechende Grafik an.
@ -1241,6 +1251,7 @@ Sind evtl. die Namen der Variablen nicht eindeutig?</string>
<string name="err_csvToManyValues">Zu viel Werte in einer Zeile!</string>
<string name="err_errorWritingFile_N">Fehler beim Schreiben der Datei {0}.</string>
<string name="err_circuitContainsNoComponents">Die Schaltung enthält keine Bauteile!</string>
<string name="err_couldNotCreateServer">Der Server konnte nicht gestartet werden!</string>
<string name="key_AddrBits">Adress-Bits</string><!-- ROM, RAMDualPort, RAMSinglePort, RAMSinglePortSel, EEPROM -->
<string name="key_AddrBits_tt">Anzahl der Adress-Bits, die verwendet werden.</string>
@ -1665,6 +1676,14 @@ Sind evtl. die Namen der Variablen nicht eindeutig?</string>
<string name="key_probeMode_DOWN">Zähle fallende Flanken</string>
<string name="key_probeMode_BOTH">Zähle beide Flanken</string>
<string name="key_telnetEscape">Telnet-Modus</string>
<string name="key_telnetEscape_tt">Wenn gesetzt, werden die Telnet Steuerkommandos ausgewertet.
Zusätzlich werden vom Server die Kommandos SGA und ECHO gesendet.
Wird diese Option deaktiviert, handelt es sich um einen einfachen TCP-Server.
</string>
<string name="key_port">Port</string>
<string name="key_port_tt">Der vom Server zu öffnende Port.</string>
<string name="key_colorScheme">Farbschema</string>
<string name="key_colorScheme_DEFAULT">Normal</string>
<string name="key_colorScheme_DARK">Dunkel</string>

View File

@ -280,6 +280,16 @@
<string name="elem_Terminal_pin_D">The data to write to the terminal</string>
<string name="elem_Terminal_pin_en">A high at this input enables the clock input.</string>
<string name="elem_Telnet">Telnet</string>
<string name="elem_Telnet_tt">Allows a Telnet connection to the circuit.
It is possible to receive and send characters via Telnet.</string>
<string name="elem_Telnet_pin_out">Data output</string>
<string name="elem_Telnet_pin_av">Outputs a one if data is present.</string>
<string name="elem_Telnet_pin_in">The data to be sent.</string>
<string name="elem_Telnet_pin_C">Clock input</string>
<string name="elem_Telnet_pin_wr">If set, the input data byte is sent.</string>
<string name="elem_Telnet_pin_rd">If set, a received byte is output.</string>
<string name="elem_VGA">VGA Monitor</string>
<string name="elem_VGA_short">VGA</string>
<string name="elem_VGA_tt">Analyzes the incoming video signals and displays the corresponding graphic.
@ -1228,6 +1238,7 @@
<string name="err_csvToManyValues">Too many values in one line!</string>
<string name="err_errorWritingFile_N">Error writing file {0}.</string>
<string name="err_circuitContainsNoComponents">The circuit contains no components!</string>
<string name="err_couldNotCreateServer">Could not start the server!</string>
<string name="key_AddrBits">Address Bits</string><!-- ROM, RAMDualPort, RAMSinglePort, RAMSinglePortSel, EEPROM -->
<string name="key_AddrBits_tt">Number of address bits used.</string>
@ -1652,6 +1663,13 @@
<string name="key_probeMode_DOWN">Count on Falling Edge</string>
<string name="key_probeMode_BOTH">Count both Edges</string>
<string name="key_telnetEscape">Telnet mode</string>
<string name="key_telnetEscape_tt">If set, the Telnet control commands are evaluated.
In addition, the server sends the SGA and ECHO commands. If this option is disabled,
the server is a simple TCP server.</string>
<string name="key_port">Port</string>
<string name="key_port_tt">The port to be opened by the server.</string>
<string name="key_colorScheme">Color scheme</string>
<string name="key_colorScheme_DEFAULT">Normal</string>
<string name="key_colorScheme_DARK">Dark</string>

View File

@ -40,7 +40,7 @@ public class TestExamples extends TestCase {
*/
public void testDistExamples() throws Exception {
File examples = new File(Resources.getRoot().getParentFile().getParentFile(), "/main/dig");
assertEquals(316, new FileScanner(this::check).scan(examples));
assertEquals(317, new FileScanner(this::check).scan(examples));
assertEquals(502, testCasesInFiles);
}

View File

@ -695,15 +695,29 @@
<int>8</int>
</entry>
</elementAttributes>
<pos x="420" y="1080"/>
<pos x="420" y="1100"/>
</visualElement>
<visualElement>
<elementName>GenericCode</elementName>
<elementAttributes/>
<pos x="120" y="1120"/>
</visualElement>
<visualElement>
<elementName>Telnet</elementName>
<elementAttributes>
<entry>
<string>port</string>
<int>2323</int>
</entry>
</elementAttributes>
<pos x="540" y="1100"/>
</visualElement>
</visualElements>
<wires>
<wire>
<p1 x="500" y="1060"/>
<p2 x="520" y="1100"/>
</wire>
<wire>
<p1 x="1080" y="640"/>
<p2 x="1100" y="640"/>
@ -752,6 +766,10 @@
<p1 x="700" y="520"/>
<p2 x="800" y="520"/>
</wire>
<wire>
<p1 x="520" y="1160"/>
<p2 x="540" y="1160"/>
</wire>
<wire>
<p1 x="400" y="1160"/>
<p2 x="420" y="1160"/>
@ -864,6 +882,10 @@
<p1 x="780" y="280"/>
<p2 x="800" y="280"/>
</wire>
<wire>
<p1 x="400" y="1180"/>
<p2 x="420" y="1180"/>
</wire>
<wire>
<p1 x="220" y="540"/>
<p2 x="260" y="540"/>
@ -916,6 +938,10 @@
<p1 x="580" y="1060"/>
<p2 x="600" y="1060"/>
</wire>
<wire>
<p1 x="380" y="1060"/>
<p2 x="500" y="1060"/>
</wire>
<wire>
<p1 x="760" y="1060"/>
<p2 x="840" y="1060"/>
@ -1073,8 +1099,8 @@
<p2 x="240" y="1080"/>
</wire>
<wire>
<p1 x="380" y="1080"/>
<p2 x="420" y="1080"/>
<p1 x="400" y="1080"/>
<p2 x="500" y="1080"/>
</wire>
<wire>
<p1 x="240" y="1080"/>
@ -1181,20 +1207,24 @@
<p2 x="1160" y="840"/>
</wire>
<wire>
<p1 x="220" y="460"/>
<p2 x="260" y="460"/>
</wire>
<wire>
<p1 x="600" y="460"/>
<p2 x="620" y="460"/>
<p1 x="380" y="1100"/>
<p2 x="420" y="1100"/>
</wire>
<wire>
<p1 x="240" y="1100"/>
<p2 x="260" y="1100"/>
</wire>
<wire>
<p1 x="400" y="1100"/>
<p2 x="420" y="1100"/>
<p1 x="520" y="1100"/>
<p2 x="540" y="1100"/>
</wire>
<wire>
<p1 x="220" y="460"/>
<p2 x="260" y="460"/>
</wire>
<wire>
<p1 x="600" y="460"/>
<p2 x="620" y="460"/>
</wire>
<wire>
<p1 x="600" y="720"/>
@ -1276,6 +1306,18 @@
<p1 x="760" y="220"/>
<p2 x="800" y="220"/>
</wire>
<wire>
<p1 x="400" y="1120"/>
<p2 x="420" y="1120"/>
</wire>
<wire>
<p1 x="500" y="1120"/>
<p2 x="520" y="1120"/>
</wire>
<wire>
<p1 x="520" y="1120"/>
<p2 x="540" y="1120"/>
</wire>
<wire>
<p1 x="1140" y="480"/>
<p2 x="1160" y="480"/>
@ -1413,8 +1455,8 @@
<p2 x="1200" y="500"/>
</wire>
<wire>
<p1 x="400" y="1140"/>
<p2 x="420" y="1140"/>
<p1 x="520" y="1140"/>
<p2 x="540" y="1140"/>
</wire>
<wire>
<p1 x="1080" y="760"/>
@ -1644,6 +1686,14 @@
<p1 x="520" y="740"/>
<p2 x="520" y="780"/>
</wire>
<wire>
<p1 x="520" y="1120"/>
<p2 x="520" y="1140"/>
</wire>
<wire>
<p1 x="520" y="1140"/>
<p2 x="520" y="1160"/>
</wire>
<wire>
<p1 x="840" y="1000"/>
<p2 x="840" y="1060"/>
@ -1714,19 +1764,23 @@
</wire>
<wire>
<p1 x="400" y="1020"/>
<p2 x="400" y="1100"/>
<p2 x="400" y="1080"/>
</wire>
<wire>
<p1 x="400" y="1080"/>
<p2 x="400" y="1120"/>
</wire>
<wire>
<p1 x="400" y="580"/>
<p2 x="400" y="780"/>
</wire>
<wire>
<p1 x="400" y="1100"/>
<p2 x="400" y="1140"/>
<p1 x="400" y="1120"/>
<p2 x="400" y="1160"/>
</wire>
<wire>
<p1 x="400" y="1140"/>
<p2 x="400" y="1160"/>
<p1 x="400" y="1160"/>
<p2 x="400" y="1180"/>
</wire>
<wire>
<p1 x="400" y="220"/>
@ -2028,6 +2082,10 @@
<p1 x="500" y="560"/>
<p2 x="500" y="580"/>
</wire>
<wire>
<p1 x="500" y="1080"/>
<p2 x="500" y="1120"/>
</wire>
<wire>
<p1 x="500" y="760"/>
<p2 x="500" y="840"/>
@ -2160,6 +2218,10 @@
<p1 x="380" y="140"/>
<p2 x="380" y="160"/>
</wire>
<wire>
<p1 x="380" y="1060"/>
<p2 x="380" y="1100"/>
</wire>
<wire>
<p1 x="380" y="160"/>
<p2 x="380" y="820"/>
@ -2170,7 +2232,7 @@
</wire>
<wire>
<p1 x="380" y="920"/>
<p2 x="380" y="1080"/>
<p2 x="380" y="1060"/>
</wire>
</wires>
<measurementOrdering/>