Subversion Repositories HomeAutomation

Rev

Details | Last modification | View Log | SVN | RSS feed

Rev Author Line No. Line
15 arune 1
/*
2
 * File created by Jimmy
3
 * at 2003-sep-02 21:08:53
4
 */
5
package Macbeth.Modules.modARNE;
6
 
7
import Macbeth.System.*;
8
import Macbeth.XML.*;
9
import Macbeth.Utilities.*;
10
import javax.comm.*;
11
import java.util.*;
12
import java.io.*;
13
 
14
/**
15
 * Macbeth module that enables macbeth modules to communicate
16
 * with ARNE nodes via the serial port.
17
 * @author Jimmy
18
 * @author arune
19
 */
20
public class modARNE extends MbModule {
21
    //TODO: timeout, if an arne-packet is not completely received during this time, that packet should be flushed
22
 
23
    //problem med att skicka två paket (eller fler) i följd, CTS från avr kommer först när första paketet skickats
24
    //klart, då har redan paket två placerats i bufferten
25
    //jag har försökt med handskakning (dvs hårdvaran ska ta hand om CTS och sluta skicka) men det vill inte fungera
26
    //testat kolla getOutputBufferSize, men den verkar alltid vara 0
27
    //testat eventen OUTPUT_BUFFER_EMPTY, för att hålla en flagga så man vet när data finns i buffer, men nix
28
 
29
    //This will handle all serial port IO
30
    private SerialPortIO spr;
31
    //Which byte are we currently receiving from ARNE?
32
    private byte currentReceiveByte;
33
    //A temporary ARNE packet that will be used when receiving from ARNE
34
    private ARNEPacket apTempARNE;
35
    //A temporary holder for pc host byte
36
    private byte pcHost=0;
37
    //A temporary holder fo pc module byte
38
    private byte pcModule=0;
39
    //A temporary ARNE packet that will be used when receiving XML from macbeth
40
    private ARNEPacket apTempMacbeth;
41
    //A list of Macbeth module names, where each name is mapped to a byte
42
    private HashMap moduleMappings;
43
    //A list of Macbeth kernel names, where each name is mapped to a byte
44
    private HashMap kernelMappings;
45
    //A list of ARNE node names, where each name is mapped to a byte
46
    private HashMap nodeMappings;
47
    //This flag is set true when an ARNE packet in XML format is currently being parsed
48
    private boolean parsingARNEXML;
49
 
50
    /**
51
     * Creates a new instance of modARNE.
52
     */
53
    public modARNE() {
54
        //construct MbModule
55
        super();
56
        //initial values...
57
        currentReceiveByte = 1;
58
        apTempARNE = null;
59
        apTempMacbeth = null;
60
        options.putField("comport", "COM1");
61
        parsingARNEXML = false;
62
        //mapping tables
63
        moduleMappings = new HashMap(64);
64
        kernelMappings = new HashMap(32);
65
        nodeMappings = new HashMap(64);
66
        //register our packet data handler
67
        setPacketDataHandler(new PacketDataHandler());
68
    }
69
 
70
    /**
71
     * Gets the name of the component.
72
     * @return The name of the component.
73
     */
74
    public String name() {
75
        return "ARNE";
76
    }
77
 
78
    /**
79
     * Gets the description of the component.
80
     * @return The description of the component.
81
     */
82
    public String description() {
83
        return "Allows for communication between modules and ARNE nodes";
84
    }
85
 
86
    /**
87
     * This method sets default values on all _required_ data
88
     * fields in the data repository.
89
     */
90
    public void initDataFields() {
91
        //set default values on all REQUIRED option fields before starting up subsystems
92
        options.putField("comport", "COM1");
93
    }
94
 
95
    /**
96
     * Starts up this module.
97
     */
98
    public void startup() throws MbStartupException {
99
        //start up MbModule
100
        super.startup();
101
 
102
        //init serial port
103
        spr = new SerialPortIO(options.getField("comport"));
104
        try {
105
            spr.start();
106
        } catch (IOException e) {
107
            //we cannot start up if serial port failed to initialize
108
            spr.stop();
109
            throw new MbStartupException("The serialport failed to initialize (" + e.getMessage() + ")");
110
        }
111
 
112
        //get all module mappings from data repository
113
        DataRepository.DataList modules = dataRepository.getList("modulemappings");
114
        Iterator it = modules.items();
115
        while (it.hasNext()) {
116
            DataRepository.DataListItem item = (DataRepository.DataListItem) it.next();
117
            String name = item.getField("name");
118
            UByte b = UByte.parseUByte(item.getField("byte"));
119
            moduleMappings.put(b, name);
120
        }
121
 
122
        //get all kernel mappings from data repository
123
        DataRepository.DataList kernels = dataRepository.getList("kernelmappings");
124
        it = kernels.items();
125
        while (it.hasNext()) {
126
            DataRepository.DataListItem item = (DataRepository.DataListItem) it.next();
127
            String name = item.getField("name");
128
            UByte b = UByte.parseUByte(item.getField("byte"));
129
            kernelMappings.put(b, name);
130
        }
131
 
132
        //get all kernel mappings from data repository
133
        DataRepository.DataList nodes = dataRepository.getList("nodemappings");
134
        it = nodes.items();
135
        while (it.hasNext()) {
136
            DataRepository.DataListItem item = (DataRepository.DataListItem) it.next();
137
            String name = item.getField("name");
138
            UByte b = UByte.parseUByte(item.getField("byte"));
139
            nodeMappings.put(name, b);
140
        }
141
 
142
    }
143
 
144
    /**
145
     * Shuts down this module.
146
     */
147
    public void shutdown() {
148
        //shut down MbModule
149
        super.shutdown();
150
        spr.stop();
151
    }
152
 
153
    /**
154
     * Takes care of bytes that we've received from ARNE.
155
     * @param bytes The received bytes.
156
     */
157
    protected void handleIncomingBytes(byte[] bytes) {
158
        for (int i=0; i<bytes.length; i++) {
159
            //byte1 is arneheader byte (control bits and data len)
160
            if (currentReceiveByte==1) {
161
                //check control bits
162
                if ((bytes[i] & 0xA0) == 0xA0) {
163
                    apTempARNE = new ARNEPacket();
164
                    //if OK, save byte and go on
165
                    apTempARNE.header[0] = new UByte(bytes[i]);
166
                    //this byte determines data length,
167
                    //so allocate memory now
168
                    int size = bytes[i] & 0x0F;
169
                    if (size>0 && size<ARNEPacket.MAX_ARNE_DATALEN) {
170
                        apTempARNE.setDataLen(size);
171
                        currentReceiveByte++;
172
                    }
173
                }
174
            }
175
            //byte2 is PC-host byte
176
            else if (currentReceiveByte==2) {
177
                pcHost = UByte.fromShort((short)bytes[i]).byteValue();
178
                currentReceiveByte++;
179
            }
180
            //byte3 is PC-module byte
181
            else if (currentReceiveByte==3) {
182
                pcModule = UByte.fromShort((short)bytes[i]).byteValue();
183
                currentReceiveByte++;
184
            }
185
            //the rest are arne-data bytes
186
            else {
187
                //check if we're still filling data buffer
188
                if (currentReceiveByte <= apTempARNE.getDataLen()+3) {
189
                    //if so, just continue to fill
190
                    apTempARNE.data[currentReceiveByte-4] = new UByte(bytes[i]);
191
                    //was this the last byte?
192
                    if (currentReceiveByte==apTempARNE.getDataLen()+3) {
193
                        //if so, handle all data, and restart
194
                        handleIncomingARNEPacket(apTempARNE, pcHost, pcModule);
195
                        currentReceiveByte = 1;
196
                    } else {
197
                        //else, go on
198
                        currentReceiveByte++;
199
                    }
200
                }
201
            }
202
        }
203
    }
204
 
205
    /**
206
     * Takes care of an ARNE packet that've been received from ARNE.
207
     * @param ap The received ARNE packet.
208
     * @param pcHost The PC Host byte. This will be translated to a Macbeth Kernel name.
209
     * @param pcModule The PC Module byte. This will be translated to a Macbeth Module name.
210
     */
211
    private void handleIncomingARNEPacket(ARNEPacket ap, byte pcHost, byte pcModule) {
212
        //_debug.println("handling incoming ARNE packet");
213
        MbPacket p = new MbPacket();
214
        p.getDestination().setKernel((String)kernelMappings.get(new UByte(pcHost)));
215
        p.getDestination().setModule((String)moduleMappings.get(new UByte(pcModule)));
216
        if (p.getDestination().getKernel()!=null & p.getDestination().getModule()!=null) {
217
            p.setContents("<arnepacket bytes=\"" + ap.data.length + "\">");
218
            for (int i=0; i<ap.data.length; i++) {
219
                p.appendContents("<byte id=\"" + Integer.toString(i+1) + "\" value=\"" + ap.data[i].toString() + "\" />");
220
            }
221
            p.appendContents("</arnepacket>");
222
            sendPacket(p);
223
        } else {
224
            _errors.println("Incoming arne packet had unknown host- and/or module-bytes!");
225
            _errors.println("(host: " + UByte.toString(pcHost) + ", module: " + UByte.toString(pcModule) + ")");
226
        }
227
    }
228
 
229
    /**
230
     * Sends an ARNE packet to the ARNE PC node.
231
     * @param ap The ARNE packet to send.
232
     */
233
    private void sendARNEPacket(ARNEPacket ap) {
234
        //_debug.println("sending ARNE packet:");
235
        byte[] bytes = new byte[2+ap.getDataLen()];
236
        bytes[0] = ap.header[0].byteValue();
237
        bytes[1] = ap.header[1].byteValue();
238
        for (int i=0; i<ap.getDataLen(); i++) {
239
            bytes[2+i] = ap.data[i].byteValue();
240
            //_debug.println("  byte" + Integer.toString(i) + "=" + ap.data[i].toString());
241
        }
242
        try {
243
            spr.sendBytes(bytes);
244
        } catch (IOException e) {
245
            _errors.println("An ARNE packet could not be delivered to the ARNE bus due to I/O-errors! (Exception was '" + e + "')");
246
        }
247
    }
248
 
249
    /**
250
     * Takes care of XML-data found in incoming packets.
251
     */
252
    private class PacketDataHandler implements XMLDataHandler {
253
        /**
254
         * Called when start of a new element is found in the XML-data.
255
         * Ex: <name attr1="value1" attr2="value2">
256
         * Element name would then be "name" and attribute list
257
         * would contain "value1" and "value2" mapped to the attribute
258
         * names "attr1" and "attr2".
259
         * @param element The name of the element.
260
         * @param attributes The element attributes.
261
         */
262
        public void XMLstartElement(String element, HashMap attributes) {
263
            /**
264
             * Ex: <arnepacket bytes="5" destnode="25">
265
             * This is the start of an ARNE packet that some other module
266
             * is sending to us.
267
             */
268
            if (element.equals("arnepacket") && attributes.containsKey("bytes") && attributes.containsKey("destnode")) {
269
                short bytes = Short.parseShort((String)attributes.get("bytes"));
270
                String destNode = (String)attributes.get("destnode");
271
                //check if specified ARNE node is valid
272
                if (nodeMappings.containsKey(destNode)) {
273
                    //check if number of bytes is valid
274
                    if (bytes>0 && bytes<=ARNEPacket.MAX_ARNE_DATALEN) {
275
                        parsingARNEXML = true;
276
                        apTempMacbeth = new ARNEPacket();
277
                        apTempMacbeth.setDataLen(bytes);
278
                        apTempMacbeth.setNodeAddress(((UByte)nodeMappings.get(destNode)).byteValue());
279
                    } else {
280
                        //invalid number of bytes specified (too few or too many)
281
                        _errors.println("Invalid number of bytes specified in a packet!");
282
                    }
283
                } else {
284
                    //invalid destination ARNE node was specified
285
                    _errors.println("A packet was addressed to the ARNE node '" + destNode + "', which is unknown!");
286
                }
287
            }
288
            /**
289
             * Ex: <byte id="1" value="5">
290
             * This is a byte inside an ARNE packet.
291
             */
292
            else if (element.equals("byte") && attributes.containsKey("id") && attributes.containsKey("value")) {
293
                //only care about byte-tags while parsing valid ARNE packets
294
                if (parsingARNEXML) {
295
                    short id = Short.parseShort((String)attributes.get("id"));
296
                    //if id is valid
297
                    if (id>0 && id<=apTempMacbeth.getDataLen()) {
298
                        //set data in arne packet
299
                        //int intval = Integer.parseInt((String)attributes.get("value"));
300
                        apTempMacbeth.data[id-1] = UByte.parseUByte((String)attributes.get("value"));
301
                    } else {
302
                        _errors.println("Invalid byte IDs specified in a packet");
303
                        parsingARNEXML = false;
304
                    }
305
                } else {
306
                    //we are not parsing a valid ARNE packet at the moment, so ignore byte-tags
307
                }
308
            }
309
        }
310
 
311
        /**
312
         * Called when end of an element was found in the XML-data.
313
         * Ex: </name> or <test attr="value" />
314
         * @param element The name of the element.
315
         */
316
        public void XMLendElement(String element) {
317
            /**
318
             * Ex: </arnepacket>
319
             * End of a received ARNE packet. At this point we should send
320
             * the whole received packet to the ARNE bus.
321
             */
322
            if (element.equals("arnepacket")) {
323
                if (parsingARNEXML) {
324
                    //check if all bytes have been parsed
325
                    boolean allBytesParsed = true;
326
                    for (int i=0; i<apTempMacbeth.getDataLen(); i++) {
327
                        if (apTempMacbeth.data[i]==null) {
328
                            allBytesParsed = false;
329
                        }
330
                    }
331
                    if (allBytesParsed) {
332
                        //if that is the case, send the packet to the ARNE bus now
333
                        parsingARNEXML = false;
334
                        sendARNEPacket(apTempMacbeth);
335
                        //_debug.println("Ive got packet!");
336
                    } else {
337
                        //if not all bytes have been parsed, packet was invalid
338
                        _errors.println("Invalid packet received! (bytes were missing)");
339
                    }
340
                }
341
            }
342
        }
343
 
344
        public void XMLelementData(String data) {}
345
        public void XMLdocumentStart() {}
346
        public void XMLdocumentEnd() {}
347
    }
348
 
349
 
350
    /**
351
     * A class for handling all serial port IO.
352
     */
353
    public class SerialPortIO implements Runnable, SerialPortEventListener {
354
        private CommPortIdentifier portId;
355
        private SerialPort serialPort;
356
        private boolean isOutputBufferEmpty;
357
        private InputStream inputStream;
358
        private OutputStream outputStream;
359
        private Thread readThread;
360
        private String portName;
361
        //a queue for byte sequences that're waiting to be sent
362
        private LinkedList byteQueue;
363
        //a thread that makes sure queued byte sequences are sent later
364
        private ByteQueueHandler byteQueueHandler;
365
 
366
        /**
367
         * Creates a new instance of SerialPortIO.
368
         * @param portname The name of the port to use.
369
         */
370
        public SerialPortIO(String portname) {
371
            portName = portname;
372
            portId = null;
373
            serialPort = null;
374
            inputStream = null;
375
            outputStream = null;
376
            byteQueue = new LinkedList();
377
            byteQueueHandler = new ByteQueueHandler(byteQueue);
378
            isOutputBufferEmpty = true;
379
        }
380
 
381
        /**
382
         * Starts listening for data on the port. Also enables
383
         * outbound data transmission on the port.
384
         * @throws IOException When, for some reason, the port fails
385
         * to initialize.
386
         */
387
        public void start() throws IOException {
388
            try {
389
                portId = CommPortIdentifier.getPortIdentifier(portName);
390
            } catch (NoSuchPortException e) {
391
                throw new IOException("The port " + portName + " was not found on this system!");
392
            }
393
            try {
394
                serialPort = (SerialPort) portId.open("modARNE", 2000);
395
            } catch (PortInUseException e) {
396
                throw new IOException("The port " + portName + " is already in use! ('" + e + "')");
397
            }
398
            try {
399
                inputStream = serialPort.getInputStream();
400
                outputStream = serialPort.getOutputStream();
401
            } catch (IOException e) {
402
                e.printStackTrace();
403
            }
404
            try {
405
                serialPort.addEventListener(this);
406
            } catch (TooManyListenersException e) {
407
                e.printStackTrace();
408
            }
409
            serialPort.notifyOnDataAvailable(true);
410
            //serialPort.notifyOnOutputEmpty(true);       //serialEvent for when the output buffer is empty (a new packet can be sent)
411
            try {
412
                serialPort.setSerialPortParams(19200,SerialPort.DATABITS_8,SerialPort.STOPBITS_1,SerialPort.PARITY_NONE);
413
                //serialPort.setFlowControlMode(SerialPort.FLOWCONTROL_NONE);
414
                serialPort.setFlowControlMode(SerialPort.FLOWCONTROL_NONE);
415
            } catch (UnsupportedCommOperationException e) {
416
                e.printStackTrace();
417
            }
418
            readThread = new Thread(this);
419
            readThread.start();
420
        }
421
 
422
        /**
423
         * Stops listening for data on the port.
424
         */
425
        public void stop() {
426
            byteQueueHandler.stopWorking();
427
            if (serialPort!=null) {
428
                serialPort.close();
429
            }
430
        }
431
 
432
        /**
433
         * Will be called from the thread.
434
         */
435
        public void run() {
436
            //don't do shit here. we will be notified about incoming data
437
            try {
438
                Thread.sleep(2000);
439
            } catch (InterruptedException e) {
440
            }
441
        }
442
 
443
        /**
444
         * Will be called when an event is triggered by the port.
445
         * @param event The event.
446
         */
447
        public void serialEvent(SerialPortEvent event) {
448
            switch(event.getEventType()) {
449
                case SerialPortEvent.BI:
450
                    break;
451
                case SerialPortEvent.OE:
452
                    break;
453
                case SerialPortEvent.FE:
454
                    break;
455
                case SerialPortEvent.PE:
456
                    break;
457
                case SerialPortEvent.CD:
458
                    break;
459
                case SerialPortEvent.CTS:
460
                    break;
461
                case SerialPortEvent.DSR:
462
                    break;
463
                case SerialPortEvent.RI:
464
                    break;
465
                case SerialPortEvent.OUTPUT_BUFFER_EMPTY:
466
                    //isOutputBufferEmpty = true;
467
                    break;
468
                case SerialPortEvent.DATA_AVAILABLE:
469
                    //there is incoming data available on the port, so read it
470
                    byte[] readBuffer = new byte[20];
471
 
472
                    try {
473
                        int numBytes = 0;
474
                        while (inputStream.available() > 0) {
475
                            numBytes = inputStream.read(readBuffer);
476
                        }
477
                        byte[] readBufferDummy = new byte[numBytes];
478
                        for (int i = 0; i < numBytes; i++) {
479
                            readBufferDummy[i] = readBuffer[i];
480
                        }
481
                        handleIncomingBytes(readBufferDummy);
482
                    } catch (IOException e) {
483
                    }
484
                    break;
485
            }
486
        }
487
 
488
        /**
489
         * Tries to sends a byte array to the port. If the CTS-flag is not
490
         * set, the byte sequence will be inserted into a queue and sent
491
         * later, when the CTS-flag has been set again.
492
         * @param bytes The bytes to send.
493
         * @throws IOException if an I/O-error occurs while trying
494
         * to send the data.
495
         */
496
        public void sendBytes(byte[] bytes) throws IOException {
497
            //check if it is clear to send and no other bytes are in send-queue
498
//if-case is commented, always put in queue
499
/*            if (serialPort.isCTS() && byteQueue.isEmpty()) {
500
                //if everything's OK, send the bytes immediately
501
                writeBytes(bytes);
502
            }*/
503
 
504
            //we cannot send yet
505
//            else {
506
                //so insert the new bytes into the queue instead
507
                if (byteQueue.size()<100) {
508
                    byteQueue.addLast(bytes);
509
                }
510
                else {
511
                    _errors.println("There are more than 100 byte sequences in send-queue. The queue will be cleared. Please check your serial port hardware!!");
512
                    byteQueue.clear();
513
                }
514
                //and start the byte queue handler if not already started
515
                if (!byteQueueHandler.isWorking()) {
516
                    byteQueueHandler.startWorking();
517
                }
518
                //_debug.println("An ARNE packet was inserted in the send-queue! It will be sent as soon as possible.");
519
//            }
520
        }
521
 
522
        /**
523
         * Sends a byte array to the port. No flow control will be used!!
524
         * You must make sure the CTS-flag is set before calling this!
525
         * @param bytes The bytes to send.
526
         * @throws IOException if an I/O-error occurs while trying
527
         * to send the data.
528
         */
529
        private synchronized void writeBytes(byte[] bytes) throws IOException {
530
            if (outputStream != null) {
531
                outputStream.write(bytes);
532
            }
533
            else {
534
                _errors.println("Serious error! Cannot write bytes to serial port since the outputstream doesn't exist!");
535
            }
536
        }
537
 
538
 
539
        //this is a thread that will take care of the byte queue
540
        private class ByteQueueHandler extends Thread {
541
            private LinkedList byteQueue;
542
            private boolean runThread;
543
 
544
            public ByteQueueHandler(LinkedList byteQueue) {
545
                super("ByteQueueHandler");
546
                this.byteQueue = byteQueue;
547
            }
548
 
549
            public void startWorking() {
550
                runThread = true;
551
                //start thread (it will call our run-method in a new thread)
552
                start();
553
            }
554
 
555
            public void stopWorking() {
556
                runThread = false;
557
            }
558
 
559
            public boolean isWorking() {
560
                return runThread;
561
            }
562
 
563
            //This is called by the thread when running.
564
            public void run() {
565
                //loop while run-flag is set true and queue is not empty
566
                while (runThread) {
567
                    //if queue is not empty and CTS-flag is set
568
                    //if (!byteQueue.isEmpty() && serialPort.isCTS() && isOutputBufferEmpty) {
569
                    //if (!byteQueue.isEmpty()) {
570
                    if (!byteQueue.isEmpty() && serialPort.isCTS()) {
571
                        //let's send the next arne packet in queue
572
                        try {
573
                            writeBytes((byte[])byteQueue.removeFirst());
574
                            //isOutputBufferEmpty = false;
575
                            //_debug.println("nr of bytes in buffer: " + serialPort.getOutputBufferSize());
576
                            //is the queue empty now?
577
                            if (byteQueue.isEmpty()) {
578
                                //_debug.println("All ARNE-packets in send-queue have now been sent!");
579
                            }
580
                        } catch (IOException e) {
581
                            _errors.println("I/O-error while trying to write bytes to port (Exception was '" + e + "')");
582
                        }
583
                        try {
584
                            sleep(50);
585
                        } catch (InterruptedException e) {
586
                            stopWorking();
587
                        }
588
                    }
589
                    //CTS-flag hasn't been set yet, or else the queue is empty
590
                    else {
591
                        //so try to get some sleep for a while =)
592
                        try {
593
                            sleep(50);
594
                        } catch (InterruptedException e) {
595
                            stopWorking();
596
                        }
597
                    }
598
                }
599
            }
600
        }
601
    }
602
 
603
}