* [PATCH 1/4] Add Reliable Asynchronous Transfer Protocol
2015-06-11 6:54 Add barebox remote control Sascha Hauer
@ 2015-06-11 6:54 ` Sascha Hauer
2015-06-12 12:05 ` Peter Korsgaard
2015-06-11 6:54 ` [PATCH 2/4] barebox remote control Sascha Hauer
` (2 subsequent siblings)
3 siblings, 1 reply; 12+ messages in thread
From: Sascha Hauer @ 2015-06-11 6:54 UTC (permalink / raw)
To: Barebox List
This patch adds support for Reliable Asynchronous Transfer Protocol (RATP)
as described in RFC916.
Communication over RS232 is often unreliable as characters are lost or
misinterpreted. This protocol allows for a reliable packet based communication
over serial lines.
The implementation simply follows the state machine described in the RFC text.
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
common/Kconfig | 10 +
common/Makefile | 2 +
common/console.c | 54 +-
include/ratp.h | 80 +++
lib/Kconfig | 8 +
lib/Makefile | 1 +
lib/ratp.c | 1612 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
7 files changed, 1765 insertions(+), 2 deletions(-)
create mode 100644 include/ratp.h
create mode 100644 lib/ratp.c
diff --git a/common/Kconfig b/common/Kconfig
index 3dfb5ac..0c96ea0 100644
--- a/common/Kconfig
+++ b/common/Kconfig
@@ -576,6 +576,16 @@ config CONSOLE_ACTIVATE_NONE
endchoice
+config CONSOLE_RATP
+ bool
+ select RATP
+ prompt "RATP console support"
+ help
+ This option adds support for remote controlling barebox via serial
+ port. The regular console is designed for human interaction whereas
+ this option adds a machine readable interface for controlling barebox.
+ Say yes here if you want to control barebox from a remote host.
+
config PARTITION
bool
prompt "Enable Partitions"
diff --git a/common/Makefile b/common/Makefile
index 2738238..e413f0d 100644
--- a/common/Makefile
+++ b/common/Makefile
@@ -44,6 +44,7 @@ obj-$(CONFIG_RESET_SOURCE) += reset_source.o
obj-$(CONFIG_SHELL_HUSH) += hush.o
obj-$(CONFIG_SHELL_SIMPLE) += parser.o
obj-$(CONFIG_STATE) += state.o
+obj-$(CONFIG_RATP) += ratp.o
obj-$(CONFIG_UIMAGE) += image.o uimage.o
obj-$(CONFIG_MENUTREE) += menutree.o
obj-$(CONFIG_EFI_GUID) += efi-guid.o
@@ -52,6 +53,7 @@ lwl-$(CONFIG_IMD) += imd-barebox.o
obj-$(CONFIG_IMD) += imd.o
obj-$(CONFIG_FILE_LIST) += file-list.o
obj-$(CONFIG_FIRMWARE) += firmware.o
+obj-$(CONFIG_CONSOLE_RATP) += ratp.o
quiet_cmd_pwd_h = PWDH $@
ifdef CONFIG_PASSWORD
diff --git a/common/console.c b/common/console.c
index 0a6fc3e..b63a075 100644
--- a/common/console.c
+++ b/common/console.c
@@ -303,6 +303,52 @@ int console_unregister(struct console_device *cdev)
}
EXPORT_SYMBOL(console_unregister);
+int barebox_ratp(struct console_device *cdev);
+
+static struct console_device *ratp_request;
+
+/**
+ * barebox_console_ratp_outstanding() - Check if there are outstanding RATP
+ * requests
+ *
+ * Return: true if there are outstanding RATP requests and false otherwise
+ */
+bool barebox_console_ratp_outstanding(void)
+{
+ if (!IS_ENABLED(CONFIG_CONSOLE_RATP))
+ return false;
+
+ return ratp_request != NULL;
+}
+
+/**
+ * barebox_console_ratp() - Check for outstanding RATP requests
+ *
+ * If there are outstanding RATP requests from the remote end
+ * enter RATP mode.
+ */
+void barebox_console_ratp(void)
+{
+ struct console_device *cdev;
+ unsigned active;
+
+ if (!IS_ENABLED(CONFIG_CONSOLE_RATP))
+ return;
+
+ if (!ratp_request)
+ return;
+
+ cdev = ratp_request;
+
+ pr_info("RATP request detected, entering RATP mode\n");
+ active = console_get_active(cdev);
+ console_set_active(cdev, 0);
+ barebox_ratp(cdev);
+ console_set_active(cdev, active);
+
+ ratp_request = NULL;
+}
+
static int getc_raw(void)
{
struct console_device *cdev;
@@ -313,8 +359,12 @@ static int getc_raw(void)
if (!(cdev->f_active & CONSOLE_STDIN))
continue;
active = 1;
- if (cdev->tstc(cdev))
- return cdev->getc(cdev);
+ if (cdev->tstc(cdev)) {
+ int ch = cdev->getc(cdev);
+ if (ch == 0x01)
+ ratp_request = cdev;
+ return ch;
+ }
}
if (!active)
/* no active console found. bail out */
diff --git a/include/ratp.h b/include/ratp.h
new file mode 100644
index 0000000..58098f9
--- /dev/null
+++ b/include/ratp.h
@@ -0,0 +1,80 @@
+#ifndef __RATP_H
+#define __RATP_H
+
+#include <linux/list.h>
+
+struct ratp_header {
+ uint8_t synch;
+ uint8_t control;
+ uint8_t data_length;
+ uint8_t cksum;
+};
+
+enum ratp_state {
+ RATP_STATE_LISTEN,
+ RATP_STATE_SYN_SENT,
+ RATP_STATE_SYN_RECEIVED,
+ RATP_STATE_ESTABLISHED,
+ RATP_STATE_FIN_WAIT,
+ RATP_STATE_LAST_ACK,
+ RATP_STATE_CLOSING,
+ RATP_STATE_TIME_WAIT,
+ RATP_STATE_CLOSED,
+};
+
+struct ratp_message {
+ void *buf;
+ size_t len;
+ struct list_head list;
+};
+
+struct ratp {
+ enum ratp_state state;
+ int sn_sent;
+ int an_sent;
+ int sn_received;
+ int an_received;
+ int active;
+ int (*send)(struct ratp *, void *pkt, int len);
+ int (*recv)(struct ratp *, uint8_t *data);
+
+ void *sendbuf;
+ int sendbuf_len;
+
+ void *recvbuf;
+
+ struct list_head recvmsg;
+
+ uint64_t timewait_timer_start;
+ uint64_t retransmission_timer_start;
+ int max_retransmission;
+ int retransmission_count;
+ int srtt;
+ int rto;
+
+ int status;
+
+ int wait_ack;
+
+ int in_ratp;
+};
+
+#define RATP_CONTROL_SO (1 << 0)
+#define RATP_CONTROL_EOR (1 << 1)
+#define RATP_CONTROL_AN (1 << 2)
+#define RATP_CONTROL_SN (1 << 3)
+#define RATP_CONTROL_RST (1 << 4)
+#define RATP_CONTROL_FIN (1 << 5)
+#define RATP_CONTROL_ACK (1 << 6)
+#define RATP_CONTROL_SYN (1 << 7)
+
+int ratp_establish(struct ratp *ratp, bool active, int timeout_ms);
+void ratp_debug(struct ratp *ratp, const char *fmt, ...);
+void ratp_sleep(int s);
+int ratp_recv_data(struct ratp *ratp, void *data, size_t *len);
+int ratp_send_data(struct ratp *ratp, const void *data, size_t len);
+int ratp_poll(struct ratp *ratp);
+bool ratp_closed(struct ratp *ratp);
+bool ratp_busy(struct ratp *ratp);
+
+#endif /* __RATP_H */
diff --git a/lib/Kconfig b/lib/Kconfig
index 62695f1..a805cfd 100644
--- a/lib/Kconfig
+++ b/lib/Kconfig
@@ -55,6 +55,14 @@ config LIBMTD
config STMP_DEVICE
bool
+config RATP
+ select CRC16
+ bool
+ help
+ Reliable Asynchronous Transfer Protocol (RATP) is a protocol for reliably
+ transferring packets over serial links described in RFC916. This implementation
+ is used for controlling barebox over serial ports.
+
source lib/gui/Kconfig
source lib/bootstrap/Kconfig
diff --git a/lib/Makefile b/lib/Makefile
index 6a3e9fd..cad03cb 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -54,3 +54,4 @@ obj-y += libfile.o
obj-y += bitmap.o
obj-y += gcd.o
obj-y += hexdump.o
+obj-$(CONFIG_RATP) += ratp.o
diff --git a/lib/ratp.c b/lib/ratp.c
new file mode 100644
index 0000000..2f4448a
--- /dev/null
+++ b/lib/ratp.c
@@ -0,0 +1,1612 @@
+/*
+ * barebox RATP implementation.
+ * This is the barebox implementation for the Reliable Asynchronous
+ * Transfer Protocol (RATP) as described in RFC916.
+ *
+ * Copyright (C) 2015 Pengutronix, Sascha Hauer <s.hauer@pengutronix.de>
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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.
+ */
+#define pr_fmt(fmt) "ratp: " fmt
+
+#include <common.h>
+#include <malloc.h>
+#include <getopt.h>
+#include <ratp.h>
+#include <crc.h>
+#include <clock.h>
+#include <asm/unaligned.h>
+
+/*
+ * RATP packet format:
+ *
+ * Byte No.
+ *
+ * +-------------------------------+
+ * | |
+ * 1 | Synch Leader | Hex 01
+ * | |
+ * +-------------------------------+
+ * | S | A | F | R | S | A | E | S |
+ * 2 | Y | C | I | S | N | N | O | O | Control
+ * | N | K | N | T | | | R | |
+ * +-------------------------------+
+ * | |
+ * 3 | Data length (0-255) |
+ * | |
+ * +-------------------------------+
+ * | |
+ * 4 | Header Checksum |
+ * | |
+ * +-------------------------------+
+ *
+ */
+
+static char *ratp_state_str[] = {
+ [RATP_STATE_LISTEN] = "LISTEN",
+ [RATP_STATE_SYN_SENT] = "SYN_SENT",
+ [RATP_STATE_SYN_RECEIVED] = "SYN_RECEIVED",
+ [RATP_STATE_ESTABLISHED] = "ESTABLISHED",
+ [RATP_STATE_FIN_WAIT] = "FIN_WAIT",
+ [RATP_STATE_LAST_ACK] = "LAST_ACK",
+ [RATP_STATE_CLOSING] = "CLOSING",
+ [RATP_STATE_TIME_WAIT] = "TIME_WAIT",
+ [RATP_STATE_CLOSED] = "CLOSED",
+};
+
+static bool ratp_sn(struct ratp_header *hdr)
+{
+ return hdr->control & RATP_CONTROL_SN ? 1 : 0;
+}
+
+static bool ratp_an(struct ratp_header *hdr)
+{
+ return hdr->control & RATP_CONTROL_AN ? 1 : 0;
+}
+
+#define ratp_set_sn(sn) (((sn) % 2) ? RATP_CONTROL_SN : 0)
+#define ratp_set_an(an) (((an) % 2) ? RATP_CONTROL_AN : 0)
+
+static inline int ratp_header_ok(struct ratp *ratp, struct ratp_header *h)
+{
+ uint8_t cksum;
+ int ret;
+
+ cksum = h->control;
+ cksum += h->data_length;
+ cksum += h->cksum;
+
+ ret = cksum == 0xff ? 1 : 0;
+
+ if (ret)
+ pr_vdebug("Header ok\n");
+ else
+ pr_vdebug("Header cksum failed: %02x\n", cksum);
+
+ return ret;
+}
+
+static bool ratp_has_data(struct ratp_header *hdr)
+{
+ if (hdr->control & RATP_CONTROL_SO)
+ return 1;
+ if (hdr->data_length)
+ return 1;
+ return 0;
+}
+
+static void ratp_print_header(struct ratp *ratp, struct ratp_header *hdr,
+ const char *prefix)
+{
+ uint8_t control = hdr->control;
+
+ pr_debug("%s>%s %s %s %s %s %s %s %s< len: %-3d\n",
+ prefix,
+ control & RATP_CONTROL_SO ? "so" : "--",
+ control & RATP_CONTROL_EOR ? "eor" : "---",
+ control & RATP_CONTROL_AN ? "an" : "--",
+ control & RATP_CONTROL_SN ? "sn" : "--",
+ control & RATP_CONTROL_RST ? "rst" : "---",
+ control & RATP_CONTROL_FIN ? "fin" : "---",
+ control & RATP_CONTROL_ACK ? "ack" : "---",
+ control & RATP_CONTROL_SYN ? "syn" : "---",
+ hdr->data_length);
+}
+
+static void ratp_create_packet(struct ratp *ratp, struct ratp_header *hdr,
+ uint8_t control, uint8_t length)
+{
+ hdr->synch = 0x1;
+ hdr->control = control;
+ hdr->data_length = length;
+ hdr->cksum = (control + length) ^ 0xff;
+}
+
+static void ratp_state_change(struct ratp *ratp, enum ratp_state state)
+{
+ pr_debug("state %-10s -> %-10s\n", ratp_state_str[ratp->state],
+ ratp_state_str[state]);
+
+ ratp->state = state;
+}
+
+#define RATP_CONTROL_SO (1 << 0)
+#define RATP_CONTROL_EOR (1 << 1)
+#define RATP_CONTROL_AN (1 << 2)
+#define RATP_CONTROL_SN (1 << 3)
+#define RATP_CONTROL_RST (1 << 4)
+#define RATP_CONTROL_FIN (1 << 5)
+#define RATP_CONTROL_ACK (1 << 6)
+#define RATP_CONTROL_SYN (1 << 7)
+
+static int ratp_send(struct ratp *ratp, void *pkt, int length)
+{
+ struct ratp_header *hdr = (void *)pkt;
+
+ ratp_print_header(ratp, hdr, "send");
+
+ ratp->sn_sent = ratp_sn(hdr);
+ ratp->an_sent = ratp_an(hdr);
+
+ if (ratp_has_data(hdr) ||
+ (hdr->control & (RATP_CONTROL_SYN | RATP_CONTROL_RST | RATP_CONTROL_FIN))) {
+ memcpy(ratp->sendbuf, pkt, length);
+ ratp->sendbuf_len = length;
+ ratp->retransmission_timer_start = get_time_ns();
+ ratp->retransmission_count = 0;
+ ratp->wait_ack = true;
+ }
+
+ return ratp->send(ratp, pkt, length);
+}
+
+static int ratp_send_hdr(struct ratp *ratp, uint8_t control)
+{
+ struct ratp_header hdr = {};
+
+ ratp_create_packet(ratp, &hdr, control, 0);
+
+ return ratp_send(ratp, &hdr, sizeof(hdr));
+}
+
+static int ratp_getc(struct ratp *ratp, uint8_t *data, int poll_timeout_ms)
+{
+ uint64_t start;
+ int ret;
+
+ start = get_time_ns();
+
+ while (1) {
+ ret = ratp->recv(ratp, data);
+ if (ret < 0)
+ return ret;
+
+ if (ret > 0)
+ return 0;
+
+ if (is_timeout(start, poll_timeout_ms * MSECOND))
+ return -ETIMEDOUT;
+ }
+}
+
+static int ratp_recv_header(struct ratp *ratp, struct ratp_header *hdr,
+ int poll_timeout_ms)
+{
+ int ret;
+ uint8_t buf;
+
+again:
+ do {
+ ret = ratp_getc(ratp, &buf, poll_timeout_ms);
+ if (ret < 0)
+ return ret;
+
+ hdr->synch = buf;
+ } while (hdr->synch != 1);
+
+ ret = ratp_getc(ratp, &buf, poll_timeout_ms);
+ if (ret < 0)
+ return ret;
+
+ hdr->control = buf;
+
+ ret = ratp_getc(ratp, &buf, poll_timeout_ms);
+ if (ret < 0)
+ return ret;
+
+ hdr->data_length = buf;
+
+ ret = ratp_getc(ratp, &buf, poll_timeout_ms);
+ if (ret < 0)
+ return ret;
+
+ hdr->cksum = buf;
+
+ if (!ratp_header_ok(ratp, hdr))
+ goto again;
+
+ return 0;
+}
+
+static int ratp_recv_pkt_data(struct ratp *ratp, void *data, uint8_t len,
+ int poll_timeout_ms)
+{
+ uint16_t crc_expect, crc_read;
+ int ret, i;
+
+ for (i = 0; i < len + 2; i++) {
+ ret = ratp_getc(ratp, data + i, poll_timeout_ms);
+ if (ret < 0)
+ return ret;
+ }
+
+ crc_expect = cyg_crc16(data, len);
+
+ crc_read = get_unaligned_be16(data + len);
+
+ if (crc_expect != crc_read) {
+ pr_vdebug("Wrong CRC: expected: 0x%04x, got 0x%04x\n",
+ crc_expect, crc_read);
+ return -EBADMSG;
+ } else {
+ pr_vdebug("correct CRC: 0x%04x\n", crc_expect);
+ }
+
+ return 0;
+}
+
+static int ratp_recv(struct ratp *ratp, void *pkt, int poll_timeout_ms)
+{
+ struct ratp_header *hdr = pkt;
+ void *data = pkt + sizeof(struct ratp_header);
+ int ret;
+
+ ret = ratp_recv_header(ratp, hdr, poll_timeout_ms);
+ if (ret < 0)
+ return ret;
+
+ ratp->sn_received = ratp_sn(hdr);
+ ratp->an_received = ratp_sn(hdr);
+
+ if (hdr->control & (RATP_CONTROL_SO | RATP_CONTROL_RST | RATP_CONTROL_SYN | RATP_CONTROL_FIN))
+ return 0;
+
+ if (hdr->data_length) {
+ ret = ratp_recv_pkt_data(ratp, data, hdr->data_length,
+ poll_timeout_ms);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static bool ratp_an_expected(struct ratp *ratp, struct ratp_header *hdr)
+{
+ return ratp_an(hdr) == (ratp->sn_sent + 1) % 2;
+}
+
+static bool ratp_sn_expected(struct ratp *ratp, struct ratp_header *hdr)
+{
+ return ratp_sn(hdr) == ratp->an_sent;
+}
+
+static int ratp_send_ack(struct ratp *ratp, struct ratp_header *hdr)
+{
+ uint8_t control = RATP_CONTROL_ACK;
+ int ret;
+
+ if (hdr->control & RATP_CONTROL_AN)
+ control |= RATP_CONTROL_SN;
+ else
+ control |= 0;
+
+ if (hdr->control & RATP_CONTROL_SN)
+ control |= 0;
+ else
+ control |= RATP_CONTROL_AN;
+
+ ret = ratp_send_hdr(ratp, control);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static void ratp_start_time_wait_timer(struct ratp *ratp)
+{
+ ratp->timewait_timer_start = get_time_ns();
+}
+
+/*
+ * This procedure details the behavior of the LISTEN state. First
+ * check the packet for the RST flag. If it is set then packet is
+ * discarded and ignored, return and continue the processing
+ * associated with this state.
+ *
+ * We assume now that the RST flag was not set. Check the packet
+ * for the ACK flag. If it is set we have an illegal condition
+ * since no connection has yet been opened. Send a RST packet
+ * with the correct response SN value:
+ *
+ * <SN=received AN><CTL=RST>
+ *
+ * Return to the current state without any further processing.
+ *
+ * We assume now that neither the RST nor the ACK flags were set.
+ * Check the packet for a SYN flag. If it is set then an attempt
+ * is being made to open a connection. Create a TCB for this
+ * connection. The sender has placed its MDL in the LENGTH field,
+ * also specified is the sender's initial SN value. Retrieve and
+ * place them into the TCB. Note that the presence of the SO flag
+ * is ignored since it has no meaning when either of the SYN, RST,
+ * or FIN flags are set.
+ *
+ * Send a SYN packet which acknowledges the SYN received. Choose
+ * the initial SN value and the MDL for this end of the
+ * connection:
+ *
+ * <SN=0><AN=received SN+1 modulo 2><CTL=SYN, ACK><LENGTH=MDL>
+ *
+ * and go to the RATP_STATE_SYN_RECEIVED state without any further
+ * processing.
+ *
+ * Any packet not satisfying the above tests is discarded and
+ * ignored. Return to the current state without any further
+ * processing.
+ */
+static void ratp_behaviour_a(struct ratp *ratp, void *pkt)
+{
+ struct ratp_header *hdr = pkt;
+
+ pr_vdebug("%s\n", __func__);
+
+ if (hdr->control & RATP_CONTROL_RST)
+ return;
+
+ if (hdr->control & RATP_CONTROL_ACK) {
+ uint8_t control = RATP_CONTROL_RST;
+
+ if (hdr->control & RATP_CONTROL_AN)
+ control |= RATP_CONTROL_SN;
+
+ ratp_send_hdr(ratp, control);
+
+ return;
+ }
+
+ if (hdr->control & RATP_CONTROL_SYN) {
+ struct ratp_header synack = {};
+ uint8_t control = RATP_CONTROL_SYN | RATP_CONTROL_ACK;
+
+ if (!(hdr->control & RATP_CONTROL_SN))
+ control |= RATP_CONTROL_AN;
+
+ ratp_create_packet(ratp, &synack, control, 255);
+ ratp_send(ratp, &synack, sizeof(struct ratp_header));
+
+ ratp_state_change(ratp, RATP_STATE_SYN_RECEIVED);
+ }
+}
+
+/*
+ * This procedure represents the behavior of the SYN-SENT state
+ * and is entered when this end of the connection decides to
+ * execute an active OPEN.
+ *
+ * First, check the packet for the ACK flag. If the ACK flag is
+ * set then check to see if the AN value was as expected. If it
+ * was continue below. Otherwise the AN value was unexpected. If
+ * the RST flag was set then discard the packet and return to the
+ * current state without any further processing, else send a
+ * reset:
+ *
+ * <SN=received AN><CTL=RST>
+ *
+ * Discard the packet and return to the current state without any
+ * further processing.
+ *
+ * At this point either the ACK flag was set and the AN value was
+ * as expected or ACK was not set. Second, check the RST flag.
+ * If the RST flag is set there are two cases:
+ *
+ * . If the ACK flag is set then discard the packet, flush the
+ * retransmission queue, inform the user "Error: Connection
+ * refused", delete the TCB, and go to the CLOSED state without
+ * any further processing.
+ *
+ * 2. If the ACK flag was not set then discard the packet and
+ * return to this state without any further processing.
+ *
+ * At this point we assume the packet contained an ACK which was
+ * Ok, or there was no ACK, and there was no RST. Now check the
+ * packet for the SYN flag. If the ACK flag was set then our SYN
+ * has been acknowledged. Store MDL received in the TCB. At this
+ * point we are technically in the ESTABLISHED state. Send an
+ * acknowledgment packet and any initial data which is queued to
+ * send:
+ *
+ * <SN=received AN><AN=received SN+1 modulo 2><CTL=ACK><DATA>
+ *
+ * Go to the ESTABLISHED state without any further processing.
+ *
+ * If the SYN flag was set but the ACK was not set then the other
+ * end of the connection has executed an active open also.
+ * Acknowledge the SYN, choose your MDL, and send:
+ *
+ * <SN=0><AN=received SN+1 modulo 2><CTL=SYN, ACK><LENGTH=MDL>
+ *
+ * Go to the SYN-RECEIVED state without any further processing.
+ *
+ * Any packet not satisfying the above tests is discarded and
+ * ignored. Return to the current state without any further
+ * processing.
+ */
+static void ratp_behaviour_b(struct ratp *ratp, void *pkt)
+{
+ struct ratp_header *hdr = pkt;
+
+ pr_vdebug("%s\n", __func__);
+
+ if ((hdr->control & RATP_CONTROL_ACK) && !ratp_an_expected(ratp, hdr)) {
+ if (!(hdr->control & RATP_CONTROL_RST)) {
+ uint8_t control = RATP_CONTROL_RST;
+
+ control = RATP_CONTROL_RST |
+ ratp_set_sn(ratp_an(hdr));
+
+ ratp_send_hdr(ratp, control);
+ }
+ return;
+ }
+
+ if (hdr->control & RATP_CONTROL_RST) {
+ if (hdr->control & RATP_CONTROL_ACK) {
+ ratp->wait_ack = false;
+ ratp->status = -ECONNREFUSED;
+
+ pr_debug("Connection refused\n");
+
+ ratp_state_change(ratp, RATP_STATE_CLOSED);
+
+ }
+ return;
+ }
+
+ if (hdr->control & RATP_CONTROL_SYN) {
+ uint8_t control;
+
+ if (hdr->control & RATP_CONTROL_ACK) {
+ control = ratp_set_sn(ratp_an(hdr)) |
+ ratp_set_an(!ratp_sn(hdr)) |
+ RATP_CONTROL_ACK;
+ } else {
+ control = ratp_set_an(!ratp_sn(hdr)) |
+ RATP_CONTROL_SYN |
+ RATP_CONTROL_ACK;
+
+ }
+
+ ratp_send_hdr(ratp, control);
+ ratp_state_change(ratp, RATP_STATE_ESTABLISHED);
+ }
+}
+
+/*
+ * Examine the received SN field value. If the SN value was
+ * expected then return and continue the processing associated
+ * with this state.
+ *
+ * We now assume the SN value was not what was expected.
+ *
+ * If either RST or FIN were set discard the packet and return to
+ * the current state without any further processing.
+ *
+ * If neither RST nor FIN flags were set it is assumed that this
+ * packet is a duplicate of one already received. Send an ACK
+ * back:
+ *
+ * <SN=received AN><AN=received SN+1 modulo 2><CTL=ACK>
+ *
+ * Discard the duplicate packet and return to the current state
+ * without any further processing.
+ */
+static int ratp_behaviour_c1(struct ratp *ratp, void *pkt)
+{
+ struct ratp_header *hdr = pkt;
+ int ret;
+
+ pr_vdebug("%s\n", __func__);
+
+ if (ratp_sn_expected(ratp, hdr)) {
+ pr_vdebug("%s: sn is expected\n", __func__);
+ return 0;
+ }
+
+ if (!(hdr->control & RATP_CONTROL_RST) &&
+ !(hdr->control & RATP_CONTROL_FIN)) {
+ ret = ratp_send_ack(ratp, hdr);
+ if (ret)
+ return ret;
+ }
+
+ return 1;
+
+}
+
+/*
+ * Examine the received SN field value. If the SN value was
+ * expected then return and continue the processing associated
+ * with this state.
+ *
+ * We now assume the SN value was not what was expected.
+ *
+ * If either RST or FIN were set discard the packet and return to
+ * the current state without any further processing.
+ *
+ * If SYN was set we assume that the other end crashed and has
+ * attempted to open a new connection. We respond by sending a
+ * legal reset:
+ *
+ * <SN=received AN><AN=received SN+1 modulo 2><CTL=RST, ACK>
+ *
+ * This will cause the other end, currently in the SYN-SENT state,
+ * to close. Flush the retransmission queue, inform the user
+ * "Error: Connection reset", discard the packet, delete the TCB,
+ * and go to the CLOSED state without any further processing.
+ *
+ * If neither RST, FIN, nor SYN flags were set it is assumed that
+ * this packet is a duplicate of one already received. Send an
+ * ACK back:
+ *
+ * <SN=received AN><AN=received SN+1 modulo 2><CTL=ACK>
+ *
+ * Discard the duplicate packet and return to the current state
+ * without any further processing.
+ */
+static int ratp_behaviour_c2(struct ratp *ratp, void *pkt)
+{
+ struct ratp_header *hdr = pkt;
+ int ret;
+
+ pr_vdebug("%s\n", __func__);
+
+ if (ratp_sn_expected(ratp, hdr))
+ return 0;
+
+ if ((hdr->control & RATP_CONTROL_RST) ||
+ (hdr->control & RATP_CONTROL_FIN))
+ return 1;
+
+ if (hdr->control & RATP_CONTROL_SYN) {
+ ratp->status = -ECONNRESET;
+ ratp->wait_ack = false;
+ pr_debug("Error: Connection reset\n");
+ ratp_state_change(ratp, RATP_STATE_CLOSED);
+ return 1;
+ }
+
+ if (!ratp_has_data(hdr))
+ return 1;
+
+ pr_vdebug("Sending ack for duplicate message\n");
+ ret = ratp_send_ack(ratp, hdr);
+ if (ret)
+ return ret;
+
+ return 1;
+}
+
+/*
+ * The packet is examined for a RST flag. If RST is not set then
+ * return and continue the processing associated with this state.
+ *
+ * RST is now assumed to have been set. If the connection was
+ * originally initiated from the LISTEN state (it was passively
+ * opened) then flush the retransmission queue, discard the
+ * packet, and go to the LISTEN state without any further
+ * processing.
+ *
+ * If instead the connection was initiated actively (came from the
+ * SYN-SENT state) then flush the retransmission queue, inform the
+ * user "Error: Connection refused", discard the packet, delete
+ * the TCB, and go to the CLOSED state without any further
+ * processing.
+ */
+static int ratp_behaviour_d1(struct ratp *ratp, void *pkt)
+{
+ struct ratp_header *hdr = pkt;
+
+ pr_vdebug("%s\n", __func__);
+
+ if (!(hdr->control & RATP_CONTROL_RST))
+ return 0;
+
+ if (!(ratp->active)) {
+ ratp_state_change(ratp, RATP_STATE_LISTEN);
+ return 1;
+ }
+
+ ratp->status = -ECONNREFUSED;
+ ratp->wait_ack = false;
+ pr_debug("Error: connection refused\n");
+
+ ratp_state_change(ratp, RATP_STATE_CLOSED);
+
+ return 1;
+}
+
+/*
+ * The packet is examined for a RST flag. If RST is not set then
+ * return and continue the processing associated with this state.
+ *
+ * RST is now assumed to have been set. Any data remaining to be
+ * sent is flushed. The retransmission queue is flushed, the user
+ * is informed "Error: Connection reset.", discard the packet,
+ * delete the TCB, and go to the CLOSED state without any further
+ * processing.
+ */
+static int ratp_behaviour_d2(struct ratp *ratp, void *pkt)
+{
+ struct ratp_header *hdr = pkt;
+
+ pr_vdebug("%s\n", __func__);
+
+ if (!(hdr->control & RATP_CONTROL_RST))
+ return 0;
+
+ ratp->status = -ECONNRESET;
+ ratp->wait_ack = false;
+ pr_debug("connection reset\n");
+
+ return 0;
+}
+
+/*
+ * The packet is examined for a RST flag. If RST is not set then
+ * return and continue the processing associated with this state.
+ *
+ * RST is now assumed to have been set. Discard the packet,
+ * delete the TCB, and go to the CLOSED state without any further
+ * processing.
+ */
+static int ratp_behaviour_d3(struct ratp *ratp, void *pkt)
+{
+ struct ratp_header *hdr = pkt;
+
+ pr_vdebug("%s\n", __func__);
+
+ if (!(hdr->control & RATP_CONTROL_RST))
+ return 0;
+
+ ratp_state_change(ratp, RATP_STATE_CLOSED);
+
+ return 1;
+}
+
+/*
+ * Check the presence of the SYN flag. If the SYN flag is not set
+ * then return and continue the processing associated with this
+ * state.
+ *
+ * We now assume that the SYN flag was set. The presence of a SYN
+ * here is an error. Flush the retransmission queue, send a legal
+ * RST packet.
+ *
+ * If the ACK flag was set then send:
+ *
+ * <SN=received AN><CTL=RST>
+ *
+ * If the ACK flag was not set then send:
+ *
+ * <SN=0><CTL=RST>
+ *
+ * The user should receive the message "Error: Connection reset.",
+ * then delete the TCB and go to the CLOSED state without any
+ * further processing.
+ */
+static int ratp_behaviour_e(struct ratp *ratp, void *pkt)
+{
+ struct ratp_header *hdr = pkt;
+ uint8_t control;
+
+ pr_vdebug("%s\n", __func__);
+
+ if (!(hdr->control & RATP_CONTROL_SYN))
+ return 0;
+
+ ratp->status = -ECONNRESET;
+ ratp->wait_ack = false;
+
+ control = RATP_CONTROL_RST;
+
+ if (hdr->control & RATP_CONTROL_ACK)
+ control |= ratp_set_sn(ratp_an(hdr));
+
+ ratp_send_hdr(ratp, control);
+
+ pr_debug("connection reset\n");
+
+ ratp_state_change(ratp, RATP_STATE_CLOSED);
+
+ return 1;
+}
+
+/*
+ * Check the presence of the ACK flag. If ACK is not set then
+ * discard the packet and return without any further processing.
+ *
+ * We now assume that the ACK flag was set. If the AN field value
+ * was as expected then return and continue the processing
+ * associated with this state.
+ *
+ * We now assume that the ACK flag was set and that the AN field
+ * value was unexpected. If the connection was originally
+ * initiated from the LISTEN state (it was passively opened) then
+ * flush the retransmission queue, discard the packet, and send a
+ * legal RST packet:
+ *
+ * <SN=received AN><CTL=RST>
+ *
+ * Then delete the TCB and go to the LISTEN state without any
+ * further processing.
+ *
+ * Otherwise the connection was initiated actively (came from the
+ * SYN-SENT state) then inform the user "Error: Connection
+ * refused", flush the retransmission queue, discard the packet,
+ * and send a legal RST packet:
+ *
+ * <SN=received AN><CTL=RST>
+ *
+ * Then delete the TCB and go to the CLOSED state without any
+ * further processing.
+ */
+static int ratp_behaviour_f1(struct ratp *ratp, void *pkt)
+{
+ struct ratp_header *hdr = pkt;
+ uint8_t control;
+
+ pr_vdebug("%s\n", __func__);
+
+ if (!(hdr->control & RATP_CONTROL_ACK))
+ return 1;
+
+ if (ratp_an_expected(ratp, hdr))
+ return 0;
+
+ control = RATP_CONTROL_RST | ratp_set_sn(ratp_an(hdr));
+ ratp_send_hdr(ratp, control);
+
+ if (ratp->active) {
+ ratp_state_change(ratp, RATP_STATE_CLOSED);
+ ratp->status = -ECONNREFUSED;
+ ratp->wait_ack = false;
+ pr_debug("connection refused\n");
+ } else {
+ ratp_state_change(ratp, RATP_STATE_LISTEN);
+ }
+
+ return 1;
+}
+
+/*
+ * Check the presence of the ACK flag. If ACK is not set then
+ * discard the packet and return without any further processing.
+ *
+ * We now assume that the ACK flag was set. If the AN field value
+ * was as expected then flush the retransmission queue and inform
+ * the user with an "Ok" if a buffer has been entirely
+ * acknowledged. Another packet containing data may now be sent.
+ * Return and continue the processing associated with this state.
+ *
+ * We now assume that the ACK flag was set and that the AN field
+ * value was unexpected. This is assumed to indicate a duplicate
+ * acknowledgment. It is ignored, return and continue the
+ * processing associated with this state.
+ */
+static int ratp_behaviour_f2(struct ratp *ratp, void *pkt)
+{
+ struct ratp_header *hdr = pkt;
+
+ pr_vdebug("%s\n", __func__);
+
+ if (!(hdr->control & RATP_CONTROL_ACK))
+ return 1;
+
+ if (ratp_an_expected(ratp, hdr)) {
+ ratp->status = 0;
+ ratp->wait_ack = false;
+ pr_vdebug("Data succesfully sent\n");
+ return 0;
+ } else {
+ pr_vdebug("%s: an not expected\n", __func__);
+ }
+
+ return 0;
+}
+
+/*
+ * Check the presence of the ACK flag. If ACK is not set then
+ * discard the packet and return without any further processing.
+ *
+ * We now assume that the ACK flag was set. If the AN field value
+ * was as expected then continue the processing associated with
+ * this state.
+ *
+ * We now assume that the ACK flag was set and that the AN field
+ * value was unexpected. This is ignored, return and continue
+ * with the processing associated with this state.
+ */
+static int ratp_behaviour_f3(struct ratp *ratp, void *pkt)
+{
+ struct ratp_header *hdr = pkt;
+
+ pr_vdebug("%s\n", __func__);
+
+ if (!(hdr->control & RATP_CONTROL_ACK))
+ return 1;
+
+ return 0;
+}
+
+/*
+ * This procedure represents the behavior of the CLOSED state of a
+ * connection. All incoming packets are discarded. If the packet
+ * had the RST flag set take no action. Otherwise it is necessary
+ * to build a RST packet. Since this end is closed the other end
+ * of the connection has incorrect data about the state of the
+ * connection and should be so informed.
+ *
+ * If the ACK flag was set then send:
+ *
+ * <SN=received AN><CTL=RST>
+ *
+ * If the ACK flag was not set then send:
+ *
+ * <SN=0><AN=received SN+1 modulo 2><CTL=RST, ACK>
+ *
+ * After sending the reset packet return to the current state
+ * without any further processing.
+ */
+static int ratp_behaviour_g(struct ratp *ratp, void *pkt)
+{
+ struct ratp_header *hdr = pkt;
+ uint8_t control;
+
+ pr_vdebug("%s\n", __func__);
+
+ control = RATP_CONTROL_RST;
+
+ if (hdr->control & RATP_CONTROL_ACK)
+ control |= ratp_set_sn(ratp_an(hdr));
+ else
+ control = ratp_set_an(ratp_sn(hdr) + 1) | RATP_CONTROL_ACK;
+
+ ratp_send_hdr(ratp, control);
+
+ return 0;
+}
+
+/*
+ * Our SYN has been acknowledged. At this point we are
+ * technically in the ESTABLISHED state. Send any initial data
+ * which is queued to send:
+ *
+ * <SN=received AN><AN=received SN+1 modulo 2><CTL=ACK><DATA>
+ *
+ * Go to the ESTABLISHED state and execute procedure I1 to process
+ * any data which might be in this packet.
+ *
+ * Any packet not satisfying the above tests is discarded and
+ * ignored. Return to the current state without any further
+ * processing.
+ */
+static int ratp_behaviour_h1(struct ratp *ratp, void *pkt)
+{
+ pr_vdebug("%s\n", __func__);
+
+ ratp->wait_ack = false;
+ ratp_state_change(ratp, RATP_STATE_ESTABLISHED);
+
+ return 0;
+}
+
+/*
+ * Check the presence of the FIN flag. If FIN is not set then
+ * continue the processing associated with this state.
+ *
+ * We now assume that the FIN flag was set. This means the other
+ * end has decided to close the connection. Flush the
+ * retransmission queue. If any data remains to be sent then
+ * inform the user "Warning: Data left unsent." The user must
+ * also be informed "Connection closing." An acknowledgment for
+ * the FIN must be sent which also indicates this end is closing:
+ *
+ * <SN=received AN><AN=received SN + 1 modulo 2><CTL=FIN, ACK>
+ *
+ * Go to the LAST-ACK state without any further processing.
+ */
+static int ratp_behaviour_h2(struct ratp *ratp, void *pkt)
+{
+ struct ratp_header *hdr = pkt;
+ uint8_t control;
+
+ pr_vdebug("%s\n", __func__);
+
+ if (!(hdr->control & RATP_CONTROL_FIN))
+ return 0;
+
+ ratp->status = -ENETDOWN;
+ ratp->wait_ack = false;
+
+ control = ratp_set_sn(ratp_an(hdr)) |
+ ratp_set_an(ratp_sn(hdr) + 1) |
+ RATP_CONTROL_FIN |
+ RATP_CONTROL_ACK;
+
+ ratp_send_hdr(ratp, control);
+
+ ratp_state_change(ratp, RATP_STATE_LAST_ACK);
+
+ return 1;
+}
+
+/*
+ * This state represents the final behavior of the FIN-WAIT state.
+ *
+ * If the packet did not contain a FIN we assume this packet is a
+ * duplicate and that the other end of the connection has not seen
+ * the FIN packet we sent earlier. Rely upon retransmission of
+ * our earlier FIN packet to inform the other end of our desire to
+ * close. Discard the packet and return without any further
+ * processing.
+ *
+ * At this point we have a packet which should contain a FIN. By
+ * the rules of this protocol an ACK of a FIN requires a FIN, ACK
+ * in response and no data. If the packet contains data we have
+ * detected an illegal condition. Send a reset:
+ * <SN=received AN><AN=received SN+1 modulo 2><CTL=RST, ACK>
+ *
+ * Discard the packet, flush the retransmission queue, inform the
+ * ser "Error: Connection reset.", delete the TCB, and go to the
+ * CLOSED state without any further processing.
+ *
+ * We now assume that the FIN flag was set and no data was
+ * contained in the packet. If the AN field value was expected
+ * then this packet acknowledges a previously sent FIN packet.
+ * The other end of the connection is then also assumed to be
+ * closing and expects an acknowledgment. Send an acknowledgment
+ * of the FIN:
+ *
+ * <SN=received AN><AN=received SN+1 modulo 2><CTL=ACK>
+ *
+ * Start the 2*SRTT timer associated with the TIME-WAIT state,
+ * discard the packet, and go to the TIME-WAIT state without any
+ * further processing.
+ *
+ * Otherwise the AN field value was unexpected. This indicates a
+ * simultaneous closing by both sides of the connection. Send an
+ * acknowledgment of the FIN:
+ *
+ * <SN=received AN><AN=received SN+1 modulo 2><CTL=ACK>
+ *
+ * Discard the packet, and go to the CLOSING state without any
+ * further processing.
+ */
+static int ratp_behaviour_h3(struct ratp *ratp, void *pkt)
+{
+ struct ratp_header *hdr = pkt;
+ uint8_t control;
+
+ pr_vdebug("%s\n", __func__);
+
+ if (!(hdr->control & RATP_CONTROL_FIN))
+ return 1;
+
+ if (ratp_has_data(hdr)) {
+ control = ratp_set_sn(ratp_an(hdr)) |
+ ratp_set_an(ratp_sn(hdr) + 1) |
+ RATP_CONTROL_RST |
+ RATP_CONTROL_ACK;
+ ratp_send_hdr(ratp, control);
+ ratp->status = -ECONNRESET;
+ ratp->wait_ack = false;
+ pr_debug("Error: Connection reset\n");
+ ratp_state_change(ratp, RATP_STATE_CLOSED);
+ return 1;
+ }
+
+ control = ratp_set_sn(ratp_an(hdr)) |
+ ratp_set_an(ratp_sn(hdr) + 1) |
+ RATP_CONTROL_ACK;
+ ratp_send_hdr(ratp, control);
+
+ if (ratp_an_expected(ratp, hdr)) {
+ ratp_state_change(ratp, RATP_STATE_TIME_WAIT);
+ ratp_start_time_wait_timer(ratp);
+ } else {
+ ratp_state_change(ratp, RATP_STATE_CLOSING);
+ }
+
+ return 1;
+}
+
+/*
+ * This state represents the final behavior of the LAST-ACK state.
+ *
+ * If the AN field value is expected then this ACK is in response
+ * to the FIN, ACK packet recently sent. This is the final
+ * acknowledging message indicating both side's agreement to close
+ * the connection. Discard the packet, flush all queues, delete
+ * the TCB, and go to the CLOSED state without any further
+ * processing.
+ *
+ * Otherwise the AN field value was unexpected. Discard the
+ * packet and remain in the current state without any further
+ * processing.
+ */
+static int ratp_behaviour_h4(struct ratp *ratp, void *pkt)
+{
+ struct ratp_header *hdr = pkt;
+
+ pr_vdebug("%s\n", __func__);
+
+ if (ratp_an_expected(ratp, hdr))
+ ratp_state_change(ratp, RATP_STATE_CLOSED);
+
+ return 1;
+}
+
+/*
+ * This state represents the final behavior of the CLOSING state.
+ *
+ * If the AN field value was expected then this packet
+ * acknowledges the FIN packet recently sent. This is the final
+ * acknowledging message indicating both side's agreement to close
+ * the connection. Start the 2*SRTT timer associated with the
+ * TIME-WAIT state, discard the packet, and go to the TIME-WAIT
+ * state without any further processing.
+ *
+ * Otherwise the AN field value was unexpected. Discard the
+ * packet and remain in the current state without any further
+ * processing.
+ */
+static int ratp_behaviour_h5(struct ratp *ratp, void *pkt)
+{
+ struct ratp_header *hdr = pkt;
+
+ pr_vdebug("%s\n", __func__);
+
+ if (ratp_an_expected(ratp, hdr)) {
+ ratp_state_change(ratp, RATP_STATE_TIME_WAIT);
+ ratp_start_time_wait_timer(ratp);
+ }
+
+ return 0;
+}
+
+/*
+ * This state represents the behavior of the TIME-WAIT state.
+ * Check the presence of the ACK flag. If ACK is not set then
+ * discard the packet and return without any further processing.
+ *
+ * Check the presence of the FIN flag. If FIN is not set then
+ * discard the packet and return without any further processing.
+ *
+ * We now assume that the FIN flag was set. This situation
+ * indicates that the last acknowledgment of the FIN packet sent
+ * by the other end of the connection did not arrive. Resend the
+ * acknowledgment:
+ *
+ * <SN=received AN><AN=received SN+1 modulo 2><CTL=ACK>
+ *
+ * Restart the 2*SRTT timer, discard the packet, and remain in the
+ * current state without any further processing.
+ */
+static int ratp_behaviour_h6(struct ratp *ratp, void *pkt)
+{
+ struct ratp_header *hdr = pkt;
+ uint8_t control;
+
+ pr_vdebug("%s\n", __func__);
+
+ if (!(hdr->control & RATP_CONTROL_ACK))
+ return 1;
+
+ if (!(hdr->control & RATP_CONTROL_FIN))
+ return 1;
+
+ control = ratp_set_sn(ratp_an(hdr) + 1) | RATP_CONTROL_ACK;
+
+ ratp_send_hdr(ratp, control);
+
+ ratp_start_time_wait_timer(ratp);
+
+ return 0;
+}
+
+static int msg_recv(struct ratp *ratp, void *pkt)
+{
+ struct ratp_header *hdr = pkt;
+ struct ratp_message *msg;
+
+ pr_debug("%s: Put msg in receive queue\n", __func__);
+
+ msg = xzalloc(sizeof(*msg));
+ if (hdr->data_length) {
+ msg->len = hdr->data_length;
+ msg->buf = xzalloc(msg->len);
+ memcpy(msg->buf, pkt + sizeof(struct ratp_header), msg->len);
+ } else {
+ msg->len = 1;
+ msg->buf = xzalloc(1);
+ *(uint8_t *)msg->buf = hdr->cksum;
+ }
+
+ list_add_tail(&msg->list, &ratp->recvmsg);
+
+ return 0;
+}
+
+/*
+ * This represents that stage of processing in the ESTABLISHED
+ * state in which all the flag bits have been processed and only
+ * data may remain. The packet is examined to see if it contains
+ * data. If not the packet is now discarded, return to the
+ * current state without any further processing.
+ *
+ * We assume the packet contained data, that either the SO flag
+ * was set or LENGTH is positive. That data is placed into the
+ * user's receive buffers. As these become full the user should
+ * be informed "Receive buffer full." An acknowledgment is sent:
+ *
+ * <SN=received AN><AN=received SN+1 modulo 2><CTL=ACK>
+ *
+ * If data is queued to send then it is most efficient to
+ * 'piggyback' this acknowledgment on that data packet.
+ *
+ * The packet is now discarded, return to the ESTABLISHED state
+ * without any further processing.
+ */
+static int ratp_behaviour_i1(struct ratp *ratp, void *pkt)
+{
+ struct ratp_header *hdr = pkt;
+ uint8_t control = 0;
+
+ if (!hdr->data_length && !(hdr->control & RATP_CONTROL_SO))
+ return 1;
+
+ pr_vdebug("%s **received** %d\n", __func__, hdr->data_length);
+
+ if (hdr->control & RATP_CONTROL_AN)
+ control |= RATP_CONTROL_SN;
+
+ if (!(hdr->control & RATP_CONTROL_SN))
+ control |= RATP_CONTROL_AN;
+
+ control |= RATP_CONTROL_ACK;
+
+ ratp_send_hdr(ratp, control);
+
+ msg_recv(ratp, pkt);
+
+ return 0;
+}
+
+/*
+ * STATE BEHAVIOR
+ * =============+========================
+ * LISTEN | A
+ * -------------+------------------------
+ * SYN-SENT | B
+ * -------------+------------------------
+ * SYN-RECEIVED | C1 D1 E F1 H1
+ * -------------+------------------------
+ * ESTABLISHED | C2 D2 E F2 H2 I1
+ * -------------+------------------------
+ * FIN-WAIT | C2 D2 E F3 H3
+ * -------------+------------------------
+ * LAST-ACK | C2 D3 E F3 H4
+ * -------------+------------------------
+ * CLOSING | C2 D3 E F3 H5
+ * -------------+------------------------
+ * TIME-WAIT | D3 E F3 H6
+ * -------------+------------------------
+ * CLOSED | G
+ * -------------+------------------------
+ */
+
+static int __ratp_state_machine(struct ratp *ratp, void *pkt)
+{
+ struct ratp_header *hdr = pkt;
+ int ret;
+
+ ratp_print_header(ratp, hdr, "recv");
+ pr_debug(" state %s\n", ratp_state_str[ratp->state]);
+
+ switch (ratp->state) {
+ case RATP_STATE_LISTEN:
+ ratp_behaviour_a(ratp, pkt);
+ break;
+ case RATP_STATE_SYN_SENT:
+ ratp_behaviour_b(ratp, pkt);
+ break;
+ case RATP_STATE_SYN_RECEIVED:
+ ret = ratp_behaviour_c1(ratp, pkt);
+ if (ret)
+ return ret;
+ ret = ratp_behaviour_d1(ratp, pkt);
+ if (ret)
+ return ret;
+ ret = ratp_behaviour_e(ratp, pkt);
+ if (ret)
+ return ret;
+ ret = ratp_behaviour_f1(ratp, pkt);
+ if (ret)
+ return ret;
+ ret = ratp_behaviour_h1(ratp, pkt);
+ if (ret)
+ return ret;
+ ret = ratp_behaviour_i1(ratp, pkt);
+ if (ret)
+ return ret;
+ break;
+ case RATP_STATE_ESTABLISHED:
+ ret = ratp_behaviour_c2(ratp, pkt);
+ if (ret)
+ return ret;
+ ret = ratp_behaviour_d2(ratp, pkt);
+ if (ret)
+ return ret;
+ ret = ratp_behaviour_e(ratp, pkt);
+ if (ret)
+ return ret;
+ ret = ratp_behaviour_f2(ratp, pkt);
+ if (ret)
+ return ret;
+ ret = ratp_behaviour_h2(ratp, pkt);
+ if (ret)
+ return ret;
+ ret = ratp_behaviour_i1(ratp, pkt);
+ if (ret)
+ return ret;
+ break;
+ case RATP_STATE_FIN_WAIT:
+ ret = ratp_behaviour_c2(ratp, pkt);
+ if (ret)
+ return ret;
+ ret = ratp_behaviour_d2(ratp, pkt);
+ if (ret)
+ return ret;
+ ret = ratp_behaviour_e(ratp, pkt);
+ if (ret)
+ return ret;
+ ret = ratp_behaviour_f3(ratp, pkt);
+ if (ret)
+ return ret;
+ ret = ratp_behaviour_h3(ratp, pkt);
+ if (ret)
+ return ret;
+ break;
+ case RATP_STATE_LAST_ACK:
+ ret = ratp_behaviour_c2(ratp, pkt);
+ if (ret)
+ return ret;
+ ret = ratp_behaviour_d3(ratp, pkt);
+ if (ret)
+ return ret;
+ ret = ratp_behaviour_e(ratp, pkt);
+ if (ret)
+ return ret;
+ ret = ratp_behaviour_f3(ratp, pkt);
+ if (ret)
+ return ret;
+ ret = ratp_behaviour_h4(ratp, pkt);
+ if (ret)
+ return ret;
+ break;
+ case RATP_STATE_CLOSING:
+ ret = ratp_behaviour_c2(ratp, pkt);
+ if (ret)
+ return ret;
+ ret = ratp_behaviour_d3(ratp, pkt);
+ if (ret)
+ return ret;
+ ret = ratp_behaviour_e(ratp, pkt);
+ if (ret)
+ return ret;
+ ret = ratp_behaviour_f3(ratp, pkt);
+ if (ret)
+ return ret;
+ ret = ratp_behaviour_h5(ratp, pkt);
+ if (ret)
+ return ret;
+ break;
+ case RATP_STATE_TIME_WAIT:
+ ret = ratp_behaviour_d3(ratp, pkt);
+ if (ret)
+ return ret;
+ ret = ratp_behaviour_e(ratp, pkt);
+ if (ret)
+ return ret;
+ ret = ratp_behaviour_f3(ratp, pkt);
+ if (ret)
+ return ret;
+ ret = ratp_behaviour_h6(ratp, pkt);
+ if (ret)
+ return ret;
+ break;
+ case RATP_STATE_CLOSED:
+ ratp_behaviour_g(ratp, pkt);
+ break;
+ };
+
+ return 0;
+}
+
+/**
+ * ratp_closed() - Check if a connection is closed
+ *
+ * Return: true if a connection is closed, false otherwise
+ */
+bool ratp_closed(struct ratp *ratp)
+{
+ return ratp->state == RATP_STATE_CLOSED;
+}
+
+/**
+ * ratp_busy() - Check if we are inside the RATP code
+ *
+ * Needed for RATP debugging. The RATP console uses this to determine
+ * if it is called from inside the RATP code.
+ *
+ * Return: true if we are inside the RATP code, false otherwise
+ */
+bool ratp_busy(struct ratp *ratp)
+{
+ return ratp->in_ratp != 0;
+}
+
+/**
+ * ratp_poll() - Execute RATP state machine
+ * @ratp: The RATP link
+ *
+ * This function should be executed periodically to keep the RATP state
+ * machine going.
+ *
+ * Return: 0 if successful, a negative error code otherwise.
+ */
+int ratp_poll(struct ratp *ratp)
+{
+ int ret;
+
+ ratp->in_ratp++;
+
+ ret = ratp_recv(ratp, ratp->recvbuf, 500);
+ if (ret < 0)
+ goto out;
+
+ if (ratp->state == RATP_STATE_TIME_WAIT &&
+ is_timeout(ratp->timewait_timer_start, ratp->srtt * 2 * MSECOND)) {
+ pr_debug("2*SRTT timer timed out\n");
+ ret = -ECONNRESET;
+ goto out;
+ }
+
+ ret = __ratp_state_machine(ratp, ratp->recvbuf);
+ if (ret < 0)
+ goto out;
+
+ ratp->sn_received = ratp_sn(ratp->recvbuf);
+
+ if (ratp->wait_ack && is_timeout(ratp->retransmission_timer_start, ratp->rto * MSECOND)) {
+ ratp->retransmission_count++;
+ if (ratp->retransmission_count == ratp->max_retransmission) {
+ ret = -ETIMEDOUT;
+ goto out;
+ }
+
+ pr_debug("%s: retransmit\n", __func__);
+
+ ratp_print_header(ratp, ratp->sendbuf, "resend");
+
+ ret = ratp->send(ratp, ratp->sendbuf, ratp->sendbuf_len);
+ if (ret)
+ goto out;
+ }
+
+ ret = 0;
+out:
+ ratp->in_ratp--;
+
+ return ret;
+}
+
+/**
+ * ratp_establish(): Establish a RATP link
+ * @ratp: The RATP link
+ * @active: if true actively create a connection
+ * @timeout_ms: Timeout in ms to wait until a connection is established. If
+ * 0 wait forever.
+ *
+ * This function establishes a link with the remote end. It expects the
+ * send and receive functions to be set, all other struct ratp members can
+ * be left uninitialized.
+ *
+ * Return: 0 if successful, a negative error code otherwise.
+ */
+int ratp_establish(struct ratp *ratp, bool active, int timeout_ms)
+{
+ int ret;
+ uint64_t start;
+
+ ratp->sendbuf = xmalloc(512);
+ ratp->recvbuf = xmalloc(512);
+ INIT_LIST_HEAD(&ratp->recvmsg);
+ ratp->max_retransmission = 100;
+ ratp->srtt = 100;
+ ratp->rto = 100;
+ ratp->active = active;
+
+ ratp->in_ratp++;
+
+ if (ratp->active) {
+ ratp_send_hdr(ratp, RATP_CONTROL_SYN);
+
+ ratp_state_change(ratp, RATP_STATE_SYN_SENT);
+ }
+
+ start = get_time_ns();
+
+ while (1) {
+ ret = ratp_poll(ratp);
+ if (ret < 0)
+ goto out;
+
+ if (ratp->state == RATP_STATE_ESTABLISHED) {
+ ret = 0;
+ goto out;
+ }
+
+ if (timeout_ms && is_timeout(start, MSECOND * timeout_ms)) {
+ ret = -ETIMEDOUT;
+ goto out;
+ }
+ }
+
+out:
+ ratp->in_ratp--;
+
+ return ret;
+}
+
+/**
+ * ratp_send_data(): Send data over a RATP link
+ * @ratp: The RATP link
+ * @data: The data buffer
+ * @len: The length of the message to send
+ *
+ * This function sends data over a RATP link. This function blocks until
+ * the message has been send or an error occured.
+ *
+ * Return: 0 if successful, a negative error code otherwise.
+ */
+int ratp_send_data(struct ratp *ratp, const void *data, size_t len)
+{
+ uint16_t crc;
+ uint8_t control = RATP_CONTROL_ACK;
+ int ret;
+ uint64_t start_rtt;
+ struct ratp_header *hdr;
+ void *buf;
+ int alpha, beta, rtt;
+ void *pkt;
+ int pktlen;
+
+ ratp->in_ratp++;
+
+ pr_vdebug("%s\n", __func__);
+
+ if (ratp->status) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ if (!len) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ control = ratp_set_sn(ratp->an_received) |
+ ratp_set_an(ratp->sn_received + 1) |
+ RATP_CONTROL_ACK;
+
+ if (len > 1) {
+ pktlen = sizeof(struct ratp_header) + len + 2;
+ pkt = xzalloc(pktlen);
+ hdr = pkt;
+ buf = pkt + sizeof(struct ratp_header);
+ memcpy(buf, data, len);
+ crc = cyg_crc16(data, len);
+ put_unaligned_be16(crc, buf + len);
+ } else {
+ pktlen = sizeof(struct ratp_header);
+ pkt = xzalloc(pktlen);
+ hdr = pkt;
+ control |= RATP_CONTROL_SO;
+ len = 0;
+ }
+
+ ratp_create_packet(ratp, hdr, control, len);
+
+ ratp->status = -EINPROGRESS;
+ ratp->wait_ack = false;
+ ratp->retransmission_count = 0;
+
+ start_rtt = get_time_ns();
+
+ ratp_send(ratp, pkt, pktlen);
+
+ while (ratp->status == -EINPROGRESS) {
+ ret = ratp_poll(ratp);
+ if (ret == -EINTR)
+ goto out;
+ }
+
+ rtt = (unsigned long)(get_time_ns() - start_rtt) / MSECOND;
+
+ alpha = 8;
+ beta = 15;
+
+ ratp->srtt = (alpha * ratp->srtt + (10 - alpha) * rtt) / 10;
+ ratp->rto = max(100, beta * ratp->srtt / 10);
+
+ pr_debug("%s: done. SRTT: %dms RTO: %dms status: %d\n",
+ __func__, ratp->srtt, ratp->rto, ratp->status);
+
+ ret = ratp->status;
+
+out:
+ ratp->in_ratp--;
+
+ return ret;
+}
+
+/**
+ * ratp_recv_data() - Receive data from a RATP link
+ * @ratp: The RATP link
+ * @data: Pointer to buffer to be filled with data
+ * @len: On entry the length of the buffer, on exit the number of
+ * bytes received.
+ *
+ * If a message is available this function fills the buffer with the data.
+ * This function does not wait for new messages. If no data is available
+ * -EAGAIN is returned.
+ *
+ * Return: 0 if successful, a negative error code otherwise.
+ */
+int ratp_recv_data(struct ratp *ratp, void *data, size_t *retlen)
+{
+ struct ratp_message *msg;
+
+ if (list_empty(&ratp->recvmsg))
+ return -EAGAIN;
+
+ msg = list_first_entry(&ratp->recvmsg, struct ratp_message, list);
+ *retlen = min(*retlen, msg->len);
+ memcpy(data, msg->buf, msg->len);
+
+ free(msg->buf);
+ list_del(&msg->list);
+ free(msg);
+
+ return 0;
+}
--
2.1.4
_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 3/4] include pyserial trunk
2015-06-11 6:54 Add barebox remote control Sascha Hauer
2015-06-11 6:54 ` [PATCH 1/4] Add Reliable Asynchronous Transfer Protocol Sascha Hauer
2015-06-11 6:54 ` [PATCH 2/4] barebox remote control Sascha Hauer
@ 2015-06-11 6:54 ` Sascha Hauer
2015-06-11 15:48 ` Uwe Kleine-König
2015-06-11 6:54 ` [PATCH 4/4] host side for barebox remote control Sascha Hauer
3 siblings, 1 reply; 12+ messages in thread
From: Sascha Hauer @ 2015-06-11 6:54 UTC (permalink / raw)
To: Barebox List
From: Jan Luebbe <jlu@pengutronix.de>
The current pyserial is broken, this version contains the fix for:
http://sourceforge.net/p/pyserial/bugs/166/
Signed-off-by: Jan Luebbe <jlu@pengutronix.de>
---
.gitignore | 1 +
scripts/serial/__init__.py | 79 ++
scripts/serial/rfc2217.py | 1327 +++++++++++++++++++++++++
scripts/serial/serialcli.py | 284 ++++++
scripts/serial/serialposix.py | 730 ++++++++++++++
scripts/serial/serialutil.py | 572 +++++++++++
scripts/serial/tools/__init__.py | 0
scripts/serial/tools/list_ports.py | 103 ++
scripts/serial/tools/list_ports_linux.py | 152 +++
scripts/serial/urlhandler/__init__.py | 0
scripts/serial/urlhandler/protocol_hwgrep.py | 45 +
scripts/serial/urlhandler/protocol_loop.py | 279 ++++++
scripts/serial/urlhandler/protocol_rfc2217.py | 11 +
scripts/serial/urlhandler/protocol_socket.py | 291 ++++++
14 files changed, 3874 insertions(+)
create mode 100644 scripts/serial/__init__.py
create mode 100644 scripts/serial/rfc2217.py
create mode 100644 scripts/serial/serialcli.py
create mode 100644 scripts/serial/serialposix.py
create mode 100644 scripts/serial/serialutil.py
create mode 100644 scripts/serial/tools/__init__.py
create mode 100644 scripts/serial/tools/list_ports.py
create mode 100644 scripts/serial/tools/list_ports_linux.py
create mode 100644 scripts/serial/urlhandler/__init__.py
create mode 100644 scripts/serial/urlhandler/protocol_hwgrep.py
create mode 100644 scripts/serial/urlhandler/protocol_loop.py
create mode 100644 scripts/serial/urlhandler/protocol_rfc2217.py
create mode 100644 scripts/serial/urlhandler/protocol_socket.py
diff --git a/.gitignore b/.gitignore
index ce2be8a..bbcfa22 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,6 +23,7 @@
*.symtypes
*.elf
*.patch
+*.pyc
*.mcp
*.bct
*.dcd
diff --git a/scripts/serial/__init__.py b/scripts/serial/__init__.py
new file mode 100644
index 0000000..33ae52e
--- /dev/null
+++ b/scripts/serial/__init__.py
@@ -0,0 +1,79 @@
+#!/usr/bin/env python
+
+# portable serial port access with python
+# this is a wrapper module for different platform implementations
+#
+# (C) 2001-2010 Chris Liechti <cliechti@gmx.net>
+# this is distributed under a free software license, see license.txt
+
+VERSION = '2.7'
+
+import sys
+
+if sys.platform == 'cli':
+ from serial.serialcli import *
+else:
+ import os
+ # chose an implementation, depending on os
+ if os.name == 'nt': #sys.platform == 'win32':
+ from serial.serialwin32 import *
+ elif os.name == 'posix':
+ from serial.serialposix import *
+ elif os.name == 'java':
+ from serial.serialjava import *
+ else:
+ raise ImportError("Sorry: no implementation for your platform ('%s') available" % (os.name,))
+
+
+protocol_handler_packages = [
+ 'serial.urlhandler',
+ ]
+
+def serial_for_url(url, *args, **kwargs):
+ """\
+ Get an instance of the Serial class, depending on port/url. The port is not
+ opened when the keyword parameter 'do_not_open' is true, by default it
+ is. All other parameters are directly passed to the __init__ method when
+ the port is instantiated.
+
+ The list of package names that is searched for protocol handlers is kept in
+ ``protocol_handler_packages``.
+
+ e.g. we want to support a URL ``foobar://``. A module
+ ``my_handlers.protocol_foobar`` is provided by the user. Then
+ ``protocol_handler_packages.append("my_handlers")`` would extend the search
+ path so that ``serial_for_url("foobar://"))`` would work.
+ """
+ # check remove extra parameter to not confuse the Serial class
+ do_open = 'do_not_open' not in kwargs or not kwargs['do_not_open']
+ if 'do_not_open' in kwargs: del kwargs['do_not_open']
+ # the default is to use the native version
+ klass = Serial # 'native' implementation
+ # check port type and get class
+ try:
+ url_nocase = url.lower()
+ except AttributeError:
+ # it's not a string, use default
+ pass
+ else:
+ if '://' in url_nocase:
+ protocol = url_nocase.split('://', 1)[0]
+ for package_name in protocol_handler_packages:
+ module_name = '%s.protocol_%s' % (package_name, protocol,)
+ try:
+ handler_module = __import__(module_name)
+ except ImportError:
+ pass
+ else:
+ klass = sys.modules[module_name].Serial
+ break
+ else:
+ raise ValueError('invalid URL, protocol %r not known' % (protocol,))
+ else:
+ klass = Serial # 'native' implementation
+ # instantiate and open when desired
+ instance = klass(None, *args, **kwargs)
+ instance.port = url
+ if do_open:
+ instance.open()
+ return instance
diff --git a/scripts/serial/rfc2217.py b/scripts/serial/rfc2217.py
new file mode 100644
index 0000000..4fe1a72
--- /dev/null
+++ b/scripts/serial/rfc2217.py
@@ -0,0 +1,1327 @@
+#! python
+#
+# Python Serial Port Extension for Win32, Linux, BSD, Jython
+# see __init__.py
+#
+# This module implements a RFC2217 compatible client. RF2217 descibes a
+# protocol to access serial ports over TCP/IP and allows setting the baud rate,
+# modem control lines etc.
+#
+# (C) 2001-2013 Chris Liechti <cliechti@gmx.net>
+# this is distributed under a free software license, see license.txt
+
+# TODO:
+# - setting control line -> answer is not checked (had problems with one of the
+# severs). consider implementing a compatibility mode flag to make check
+# conditional
+# - write timeout not implemented at all
+
+##############################################################################
+# observations and issues with servers
+#=============================================================================
+# sredird V2.2.1
+# - http://www.ibiblio.org/pub/Linux/system/serial/ sredird-2.2.2.tar.gz
+# - does not acknowledge SET_CONTROL (RTS/DTR) correctly, always responding
+# [105 1] instead of the actual value.
+# - SET_BAUDRATE answer contains 4 extra null bytes -> probably for larger
+# numbers than 2**32?
+# - To get the signature [COM_PORT_OPTION 0] has to be sent.
+# - run a server: while true; do nc -l -p 7000 -c "sredird debug /dev/ttyUSB0 /var/lock/sredir"; done
+#=============================================================================
+# telnetcpcd (untested)
+# - http://ftp.wayne.edu/kermit/sredird/telnetcpcd-1.09.tar.gz
+# - To get the signature [COM_PORT_OPTION] w/o data has to be sent.
+#=============================================================================
+# ser2net
+# - does not negotiate BINARY or COM_PORT_OPTION for his side but at least
+# acknowledges that the client activates these options
+# - The configuration may be that the server prints a banner. As this client
+# implementation does a flushInput on connect, this banner is hidden from
+# the user application.
+# - NOTIFY_MODEMSTATE: the poll interval of the server seems to be one
+# second.
+# - To get the signature [COM_PORT_OPTION 0] has to be sent.
+# - run a server: run ser2net daemon, in /etc/ser2net.conf:
+# 2000:telnet:0:/dev/ttyS0:9600 remctl banner
+##############################################################################
+
+# How to identify ports? pySerial might want to support other protocols in the
+# future, so lets use an URL scheme.
+# for RFC2217 compliant servers we will use this:
+# rfc2217://<host>:<port>[/option[/option...]]
+#
+# options:
+# - "debug" print diagnostic messages
+# - "ign_set_control": do not look at the answers to SET_CONTROL
+# - "poll_modem": issue NOTIFY_MODEMSTATE requests when CTS/DTR/RI/CD is read.
+# Without this option it expects that the server sends notifications
+# automatically on change (which most servers do and is according to the
+# RFC).
+# the order of the options is not relevant
+
+from serial.serialutil import *
+import time
+import struct
+import socket
+import threading
+import Queue
+import logging
+
+# port string is expected to be something like this:
+# rfc2217://host:port
+# host may be an IP or including domain, whatever.
+# port is 0...65535
+
+# map log level names to constants. used in fromURL()
+LOGGER_LEVELS = {
+ 'debug': logging.DEBUG,
+ 'info': logging.INFO,
+ 'warning': logging.WARNING,
+ 'error': logging.ERROR,
+ }
+
+
+# telnet protocol characters
+IAC = to_bytes([255]) # Interpret As Command
+DONT = to_bytes([254])
+DO = to_bytes([253])
+WONT = to_bytes([252])
+WILL = to_bytes([251])
+IAC_DOUBLED = to_bytes([IAC, IAC])
+
+SE = to_bytes([240]) # Subnegotiation End
+NOP = to_bytes([241]) # No Operation
+DM = to_bytes([242]) # Data Mark
+BRK = to_bytes([243]) # Break
+IP = to_bytes([244]) # Interrupt process
+AO = to_bytes([245]) # Abort output
+AYT = to_bytes([246]) # Are You There
+EC = to_bytes([247]) # Erase Character
+EL = to_bytes([248]) # Erase Line
+GA = to_bytes([249]) # Go Ahead
+SB = to_bytes([250]) # Subnegotiation Begin
+
+# selected telnet options
+BINARY = to_bytes([0]) # 8-bit data path
+ECHO = to_bytes([1]) # echo
+SGA = to_bytes([3]) # suppress go ahead
+
+# RFC2217
+COM_PORT_OPTION = to_bytes([44])
+
+# Client to Access Server
+SET_BAUDRATE = to_bytes([1])
+SET_DATASIZE = to_bytes([2])
+SET_PARITY = to_bytes([3])
+SET_STOPSIZE = to_bytes([4])
+SET_CONTROL = to_bytes([5])
+NOTIFY_LINESTATE = to_bytes([6])
+NOTIFY_MODEMSTATE = to_bytes([7])
+FLOWCONTROL_SUSPEND = to_bytes([8])
+FLOWCONTROL_RESUME = to_bytes([9])
+SET_LINESTATE_MASK = to_bytes([10])
+SET_MODEMSTATE_MASK = to_bytes([11])
+PURGE_DATA = to_bytes([12])
+
+SERVER_SET_BAUDRATE = to_bytes([101])
+SERVER_SET_DATASIZE = to_bytes([102])
+SERVER_SET_PARITY = to_bytes([103])
+SERVER_SET_STOPSIZE = to_bytes([104])
+SERVER_SET_CONTROL = to_bytes([105])
+SERVER_NOTIFY_LINESTATE = to_bytes([106])
+SERVER_NOTIFY_MODEMSTATE = to_bytes([107])
+SERVER_FLOWCONTROL_SUSPEND = to_bytes([108])
+SERVER_FLOWCONTROL_RESUME = to_bytes([109])
+SERVER_SET_LINESTATE_MASK = to_bytes([110])
+SERVER_SET_MODEMSTATE_MASK = to_bytes([111])
+SERVER_PURGE_DATA = to_bytes([112])
+
+RFC2217_ANSWER_MAP = {
+ SET_BAUDRATE: SERVER_SET_BAUDRATE,
+ SET_DATASIZE: SERVER_SET_DATASIZE,
+ SET_PARITY: SERVER_SET_PARITY,
+ SET_STOPSIZE: SERVER_SET_STOPSIZE,
+ SET_CONTROL: SERVER_SET_CONTROL,
+ NOTIFY_LINESTATE: SERVER_NOTIFY_LINESTATE,
+ NOTIFY_MODEMSTATE: SERVER_NOTIFY_MODEMSTATE,
+ FLOWCONTROL_SUSPEND: SERVER_FLOWCONTROL_SUSPEND,
+ FLOWCONTROL_RESUME: SERVER_FLOWCONTROL_RESUME,
+ SET_LINESTATE_MASK: SERVER_SET_LINESTATE_MASK,
+ SET_MODEMSTATE_MASK: SERVER_SET_MODEMSTATE_MASK,
+ PURGE_DATA: SERVER_PURGE_DATA,
+}
+
+SET_CONTROL_REQ_FLOW_SETTING = to_bytes([0]) # Request Com Port Flow Control Setting (outbound/both)
+SET_CONTROL_USE_NO_FLOW_CONTROL = to_bytes([1]) # Use No Flow Control (outbound/both)
+SET_CONTROL_USE_SW_FLOW_CONTROL = to_bytes([2]) # Use XON/XOFF Flow Control (outbound/both)
+SET_CONTROL_USE_HW_FLOW_CONTROL = to_bytes([3]) # Use HARDWARE Flow Control (outbound/both)
+SET_CONTROL_REQ_BREAK_STATE = to_bytes([4]) # Request BREAK State
+SET_CONTROL_BREAK_ON = to_bytes([5]) # Set BREAK State ON
+SET_CONTROL_BREAK_OFF = to_bytes([6]) # Set BREAK State OFF
+SET_CONTROL_REQ_DTR = to_bytes([7]) # Request DTR Signal State
+SET_CONTROL_DTR_ON = to_bytes([8]) # Set DTR Signal State ON
+SET_CONTROL_DTR_OFF = to_bytes([9]) # Set DTR Signal State OFF
+SET_CONTROL_REQ_RTS = to_bytes([10]) # Request RTS Signal State
+SET_CONTROL_RTS_ON = to_bytes([11]) # Set RTS Signal State ON
+SET_CONTROL_RTS_OFF = to_bytes([12]) # Set RTS Signal State OFF
+SET_CONTROL_REQ_FLOW_SETTING_IN = to_bytes([13]) # Request Com Port Flow Control Setting (inbound)
+SET_CONTROL_USE_NO_FLOW_CONTROL_IN = to_bytes([14]) # Use No Flow Control (inbound)
+SET_CONTROL_USE_SW_FLOW_CONTOL_IN = to_bytes([15]) # Use XON/XOFF Flow Control (inbound)
+SET_CONTROL_USE_HW_FLOW_CONTOL_IN = to_bytes([16]) # Use HARDWARE Flow Control (inbound)
+SET_CONTROL_USE_DCD_FLOW_CONTROL = to_bytes([17]) # Use DCD Flow Control (outbound/both)
+SET_CONTROL_USE_DTR_FLOW_CONTROL = to_bytes([18]) # Use DTR Flow Control (inbound)
+SET_CONTROL_USE_DSR_FLOW_CONTROL = to_bytes([19]) # Use DSR Flow Control (outbound/both)
+
+LINESTATE_MASK_TIMEOUT = 128 # Time-out Error
+LINESTATE_MASK_SHIFTREG_EMPTY = 64 # Transfer Shift Register Empty
+LINESTATE_MASK_TRANSREG_EMPTY = 32 # Transfer Holding Register Empty
+LINESTATE_MASK_BREAK_DETECT = 16 # Break-detect Error
+LINESTATE_MASK_FRAMING_ERROR = 8 # Framing Error
+LINESTATE_MASK_PARTIY_ERROR = 4 # Parity Error
+LINESTATE_MASK_OVERRUN_ERROR = 2 # Overrun Error
+LINESTATE_MASK_DATA_READY = 1 # Data Ready
+
+MODEMSTATE_MASK_CD = 128 # Receive Line Signal Detect (also known as Carrier Detect)
+MODEMSTATE_MASK_RI = 64 # Ring Indicator
+MODEMSTATE_MASK_DSR = 32 # Data-Set-Ready Signal State
+MODEMSTATE_MASK_CTS = 16 # Clear-To-Send Signal State
+MODEMSTATE_MASK_CD_CHANGE = 8 # Delta Receive Line Signal Detect
+MODEMSTATE_MASK_RI_CHANGE = 4 # Trailing-edge Ring Detector
+MODEMSTATE_MASK_DSR_CHANGE = 2 # Delta Data-Set-Ready
+MODEMSTATE_MASK_CTS_CHANGE = 1 # Delta Clear-To-Send
+
+PURGE_RECEIVE_BUFFER = to_bytes([1]) # Purge access server receive data buffer
+PURGE_TRANSMIT_BUFFER = to_bytes([2]) # Purge access server transmit data buffer
+PURGE_BOTH_BUFFERS = to_bytes([3]) # Purge both the access server receive data buffer and the access server transmit data buffer
+
+
+RFC2217_PARITY_MAP = {
+ PARITY_NONE: 1,
+ PARITY_ODD: 2,
+ PARITY_EVEN: 3,
+ PARITY_MARK: 4,
+ PARITY_SPACE: 5,
+}
+RFC2217_REVERSE_PARITY_MAP = dict((v,k) for k,v in RFC2217_PARITY_MAP.items())
+
+RFC2217_STOPBIT_MAP = {
+ STOPBITS_ONE: 1,
+ STOPBITS_ONE_POINT_FIVE: 3,
+ STOPBITS_TWO: 2,
+}
+RFC2217_REVERSE_STOPBIT_MAP = dict((v,k) for k,v in RFC2217_STOPBIT_MAP.items())
+
+# Telnet filter states
+M_NORMAL = 0
+M_IAC_SEEN = 1
+M_NEGOTIATE = 2
+
+# TelnetOption and TelnetSubnegotiation states
+REQUESTED = 'REQUESTED'
+ACTIVE = 'ACTIVE'
+INACTIVE = 'INACTIVE'
+REALLY_INACTIVE = 'REALLY_INACTIVE'
+
+class TelnetOption(object):
+ """Manage a single telnet option, keeps track of DO/DONT WILL/WONT."""
+
+ def __init__(self, connection, name, option, send_yes, send_no, ack_yes, ack_no, initial_state, activation_callback=None):
+ """\
+ Initialize option.
+ :param connection: connection used to transmit answers
+ :param name: a readable name for debug outputs
+ :param send_yes: what to send when option is to be enabled.
+ :param send_no: what to send when option is to be disabled.
+ :param ack_yes: what to expect when remote agrees on option.
+ :param ack_no: what to expect when remote disagrees on option.
+ :param initial_state: options initialized with REQUESTED are tried to
+ be enabled on startup. use INACTIVE for all others.
+ """
+ self.connection = connection
+ self.name = name
+ self.option = option
+ self.send_yes = send_yes
+ self.send_no = send_no
+ self.ack_yes = ack_yes
+ self.ack_no = ack_no
+ self.state = initial_state
+ self.active = False
+ self.activation_callback = activation_callback
+
+ def __repr__(self):
+ """String for debug outputs"""
+ return "%s:%s(%s)" % (self.name, self.active, self.state)
+
+ def process_incoming(self, command):
+ """\
+ A DO/DONT/WILL/WONT was received for this option, update state and
+ answer when needed.
+ """
+ if command == self.ack_yes:
+ if self.state is REQUESTED:
+ self.state = ACTIVE
+ self.active = True
+ if self.activation_callback is not None:
+ self.activation_callback()
+ elif self.state is ACTIVE:
+ pass
+ elif self.state is INACTIVE:
+ self.state = ACTIVE
+ self.connection.telnetSendOption(self.send_yes, self.option)
+ self.active = True
+ if self.activation_callback is not None:
+ self.activation_callback()
+ elif self.state is REALLY_INACTIVE:
+ self.connection.telnetSendOption(self.send_no, self.option)
+ else:
+ raise ValueError('option in illegal state %r' % self)
+ elif command == self.ack_no:
+ if self.state is REQUESTED:
+ self.state = INACTIVE
+ self.active = False
+ elif self.state is ACTIVE:
+ self.state = INACTIVE
+ self.connection.telnetSendOption(self.send_no, self.option)
+ self.active = False
+ elif self.state is INACTIVE:
+ pass
+ elif self.state is REALLY_INACTIVE:
+ pass
+ else:
+ raise ValueError('option in illegal state %r' % self)
+
+
+class TelnetSubnegotiation(object):
+ """\
+ A object to handle subnegotiation of options. In this case actually
+ sub-sub options for RFC 2217. It is used to track com port options.
+ """
+
+ def __init__(self, connection, name, option, ack_option=None):
+ if ack_option is None: ack_option = option
+ self.connection = connection
+ self.name = name
+ self.option = option
+ self.value = None
+ self.ack_option = ack_option
+ self.state = INACTIVE
+
+ def __repr__(self):
+ """String for debug outputs."""
+ return "%s:%s" % (self.name, self.state)
+
+ def set(self, value):
+ """\
+ Request a change of the value. a request is sent to the server. if
+ the client needs to know if the change is performed he has to check the
+ state of this object.
+ """
+ self.value = value
+ self.state = REQUESTED
+ self.connection.rfc2217SendSubnegotiation(self.option, self.value)
+ if self.connection.logger:
+ self.connection.logger.debug("SB Requesting %s -> %r" % (self.name, self.value))
+
+ def isReady(self):
+ """\
+ Check if answer from server has been received. when server rejects
+ the change, raise a ValueError.
+ """
+ if self.state == REALLY_INACTIVE:
+ raise ValueError("remote rejected value for option %r" % (self.name))
+ return self.state == ACTIVE
+ # add property to have a similar interface as TelnetOption
+ active = property(isReady)
+
+ def wait(self, timeout=3):
+ """\
+ Wait until the subnegotiation has been acknowledged or timeout. It
+ can also throw a value error when the answer from the server does not
+ match the value sent.
+ """
+ timeout_time = time.time() + timeout
+ while time.time() < timeout_time:
+ time.sleep(0.05) # prevent 100% CPU load
+ if self.isReady():
+ break
+ else:
+ raise SerialException("timeout while waiting for option %r" % (self.name))
+
+ def checkAnswer(self, suboption):
+ """\
+ Check an incoming subnegotiation block. The parameter already has
+ cut off the header like sub option number and com port option value.
+ """
+ if self.value == suboption[:len(self.value)]:
+ self.state = ACTIVE
+ else:
+ # error propagation done in isReady
+ self.state = REALLY_INACTIVE
+ if self.connection.logger:
+ self.connection.logger.debug("SB Answer %s -> %r -> %s" % (self.name, suboption, self.state))
+
+
+class RFC2217Serial(SerialBase):
+ """Serial port implementation for RFC 2217 remote serial ports."""
+
+ BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
+ 9600, 19200, 38400, 57600, 115200)
+
+ def open(self):
+ """\
+ Open port with current settings. This may throw a SerialException
+ if the port cannot be opened.
+ """
+ self.logger = None
+ self._ignore_set_control_answer = False
+ self._poll_modem_state = False
+ self._network_timeout = 3
+ if self._port is None:
+ raise SerialException("Port must be configured before it can be used.")
+ if self._isOpen:
+ raise SerialException("Port is already open.")
+ try:
+ self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self._socket.connect(self.fromURL(self.portstr))
+ self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+ except Exception, msg:
+ self._socket = None
+ raise SerialException("Could not open port %s: %s" % (self.portstr, msg))
+
+ self._socket.settimeout(5) # XXX good value?
+
+ # use a thread save queue as buffer. it also simplifies implementing
+ # the read timeout
+ self._read_buffer = Queue.Queue()
+ # to ensure that user writes does not interfere with internal
+ # telnet/rfc2217 options establish a lock
+ self._write_lock = threading.Lock()
+ # name the following separately so that, below, a check can be easily done
+ mandadory_options = [
+ TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE),
+ TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED),
+ ]
+ # all supported telnet options
+ self._telnet_options = [
+ TelnetOption(self, 'ECHO', ECHO, DO, DONT, WILL, WONT, REQUESTED),
+ TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED),
+ TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, REQUESTED),
+ TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, INACTIVE),
+ TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, REQUESTED),
+ ] + mandadory_options
+ # RFC 2217 specific states
+ # COM port settings
+ self._rfc2217_port_settings = {
+ 'baudrate': TelnetSubnegotiation(self, 'baudrate', SET_BAUDRATE, SERVER_SET_BAUDRATE),
+ 'datasize': TelnetSubnegotiation(self, 'datasize', SET_DATASIZE, SERVER_SET_DATASIZE),
+ 'parity': TelnetSubnegotiation(self, 'parity', SET_PARITY, SERVER_SET_PARITY),
+ 'stopsize': TelnetSubnegotiation(self, 'stopsize', SET_STOPSIZE, SERVER_SET_STOPSIZE),
+ }
+ # There are more subnegotiation objects, combine all in one dictionary
+ # for easy access
+ self._rfc2217_options = {
+ 'purge': TelnetSubnegotiation(self, 'purge', PURGE_DATA, SERVER_PURGE_DATA),
+ 'control': TelnetSubnegotiation(self, 'control', SET_CONTROL, SERVER_SET_CONTROL),
+ }
+ self._rfc2217_options.update(self._rfc2217_port_settings)
+ # cache for line and modem states that the server sends to us
+ self._linestate = 0
+ self._modemstate = None
+ self._modemstate_expires = 0
+ # RFC 2217 flow control between server and client
+ self._remote_suspend_flow = False
+
+ self._thread = threading.Thread(target=self._telnetReadLoop)
+ self._thread.setDaemon(True)
+ self._thread.setName('pySerial RFC 2217 reader thread for %s' % (self._port,))
+ self._thread.start()
+
+ # negotiate Telnet/RFC 2217 -> send initial requests
+ for option in self._telnet_options:
+ if option.state is REQUESTED:
+ self.telnetSendOption(option.send_yes, option.option)
+ # now wait until important options are negotiated
+ timeout_time = time.time() + self._network_timeout
+ while time.time() < timeout_time:
+ time.sleep(0.05) # prevent 100% CPU load
+ if sum(o.active for o in mandadory_options) == sum(o.state != INACTIVE for o in mandadory_options):
+ break
+ else:
+ raise SerialException("Remote does not seem to support RFC2217 or BINARY mode %r" % mandadory_options)
+ if self.logger:
+ self.logger.info("Negotiated options: %s" % self._telnet_options)
+
+ # fine, go on, set RFC 2271 specific things
+ self._reconfigurePort()
+ # all things set up get, now a clean start
+ self._isOpen = True
+ if not self._rtscts:
+ self.setRTS(True)
+ self.setDTR(True)
+ self.flushInput()
+ self.flushOutput()
+
+ def _reconfigurePort(self):
+ """Set communication parameters on opened port."""
+ if self._socket is None:
+ raise SerialException("Can only operate on open ports")
+
+ # if self._timeout != 0 and self._interCharTimeout is not None:
+ # XXX
+
+ if self._writeTimeout is not None:
+ raise NotImplementedError('writeTimeout is currently not supported')
+ # XXX
+
+ # Setup the connection
+ # to get good performance, all parameter changes are sent first...
+ if not isinstance(self._baudrate, (int, long)) or not 0 < self._baudrate < 2**32:
+ raise ValueError("invalid baudrate: %r" % (self._baudrate))
+ self._rfc2217_port_settings['baudrate'].set(struct.pack('!I', self._baudrate))
+ self._rfc2217_port_settings['datasize'].set(struct.pack('!B', self._bytesize))
+ self._rfc2217_port_settings['parity'].set(struct.pack('!B', RFC2217_PARITY_MAP[self._parity]))
+ self._rfc2217_port_settings['stopsize'].set(struct.pack('!B', RFC2217_STOPBIT_MAP[self._stopbits]))
+
+ # and now wait until parameters are active
+ items = self._rfc2217_port_settings.values()
+ if self.logger:
+ self.logger.debug("Negotiating settings: %s" % (items,))
+ timeout_time = time.time() + self._network_timeout
+ while time.time() < timeout_time:
+ time.sleep(0.05) # prevent 100% CPU load
+ if sum(o.active for o in items) == len(items):
+ break
+ else:
+ raise SerialException("Remote does not accept parameter change (RFC2217): %r" % items)
+ if self.logger:
+ self.logger.info("Negotiated settings: %s" % (items,))
+
+ if self._rtscts and self._xonxoff:
+ raise ValueError('xonxoff and rtscts together are not supported')
+ elif self._rtscts:
+ self.rfc2217SetControl(SET_CONTROL_USE_HW_FLOW_CONTROL)
+ elif self._xonxoff:
+ self.rfc2217SetControl(SET_CONTROL_USE_SW_FLOW_CONTROL)
+ else:
+ self.rfc2217SetControl(SET_CONTROL_USE_NO_FLOW_CONTROL)
+
+ def close(self):
+ """Close port"""
+ if self._isOpen:
+ if self._socket:
+ try:
+ self._socket.shutdown(socket.SHUT_RDWR)
+ self._socket.close()
+ except:
+ # ignore errors.
+ pass
+ self._socket = None
+ if self._thread:
+ self._thread.join()
+ self._isOpen = False
+ # in case of quick reconnects, give the server some time
+ time.sleep(0.3)
+
+ def makeDeviceName(self, port):
+ raise SerialException("there is no sensible way to turn numbers into URLs")
+
+ def fromURL(self, url):
+ """extract host and port from an URL string"""
+ if url.lower().startswith("rfc2217://"): url = url[10:]
+ try:
+ # is there a "path" (our options)?
+ if '/' in url:
+ # cut away options
+ url, options = url.split('/', 1)
+ # process options now, directly altering self
+ for option in options.split('/'):
+ if '=' in option:
+ option, value = option.split('=', 1)
+ else:
+ value = None
+ if option == 'logging':
+ logging.basicConfig() # XXX is that good to call it here?
+ self.logger = logging.getLogger('pySerial.rfc2217')
+ self.logger.setLevel(LOGGER_LEVELS[value])
+ self.logger.debug('enabled logging')
+ elif option == 'ign_set_control':
+ self._ignore_set_control_answer = True
+ elif option == 'poll_modem':
+ self._poll_modem_state = True
+ elif option == 'timeout':
+ self._network_timeout = float(value)
+ else:
+ raise ValueError('unknown option: %r' % (option,))
+ # get host and port
+ host, port = url.split(':', 1) # may raise ValueError because of unpacking
+ port = int(port) # and this if it's not a number
+ if not 0 <= port < 65536: raise ValueError("port not in range 0...65535")
+ except ValueError, e:
+ raise SerialException('expected a string in the form "[rfc2217://]<host>:<port>[/option[/option...]]": %s' % e)
+ return (host, port)
+
+ # - - - - - - - - - - - - - - - - - - - - - - - -
+
+ def inWaiting(self):
+ """Return the number of characters currently in the input buffer."""
+ if not self._isOpen: raise portNotOpenError
+ return self._read_buffer.qsize()
+
+ def read(self, size=1):
+ """\
+ Read size bytes from the serial port. If a timeout is set it may
+ return less characters as requested. With no timeout it will block
+ until the requested number of bytes is read.
+ """
+ if not self._isOpen: raise portNotOpenError
+ data = bytearray()
+ try:
+ while len(data) < size:
+ if self._thread is None:
+ raise SerialException('connection failed (reader thread died)')
+ data.append(self._read_buffer.get(True, self._timeout))
+ except Queue.Empty: # -> timeout
+ pass
+ return bytes(data)
+
+ def write(self, data):
+ """\
+ Output the given string over the serial port. Can block if the
+ connection is blocked. May raise SerialException if the connection is
+ closed.
+ """
+ if not self._isOpen: raise portNotOpenError
+ self._write_lock.acquire()
+ try:
+ try:
+ self._socket.sendall(to_bytes(data).replace(IAC, IAC_DOUBLED))
+ except socket.error, e:
+ raise SerialException("connection failed (socket error): %s" % e) # XXX what exception if socket connection fails
+ finally:
+ self._write_lock.release()
+ return len(data)
+
+ def flushInput(self):
+ """Clear input buffer, discarding all that is in the buffer."""
+ if not self._isOpen: raise portNotOpenError
+ self.rfc2217SendPurge(PURGE_RECEIVE_BUFFER)
+ # empty read buffer
+ while self._read_buffer.qsize():
+ self._read_buffer.get(False)
+
+ def flushOutput(self):
+ """\
+ Clear output buffer, aborting the current output and
+ discarding all that is in the buffer.
+ """
+ if not self._isOpen: raise portNotOpenError
+ self.rfc2217SendPurge(PURGE_TRANSMIT_BUFFER)
+
+ def sendBreak(self, duration=0.25):
+ """\
+ Send break condition. Timed, returns to idle state after given
+ duration.
+ """
+ if not self._isOpen: raise portNotOpenError
+ self.setBreak(True)
+ time.sleep(duration)
+ self.setBreak(False)
+
+ def setBreak(self, level=True):
+ """\
+ Set break: Controls TXD. When active, to transmitting is
+ possible.
+ """
+ if not self._isOpen: raise portNotOpenError
+ if self.logger:
+ self.logger.info('set BREAK to %s' % ('inactive', 'active')[bool(level)])
+ if level:
+ self.rfc2217SetControl(SET_CONTROL_BREAK_ON)
+ else:
+ self.rfc2217SetControl(SET_CONTROL_BREAK_OFF)
+
+ def setRTS(self, level=True):
+ """Set terminal status line: Request To Send."""
+ if not self._isOpen: raise portNotOpenError
+ if self.logger:
+ self.logger.info('set RTS to %s' % ('inactive', 'active')[bool(level)])
+ if level:
+ self.rfc2217SetControl(SET_CONTROL_RTS_ON)
+ else:
+ self.rfc2217SetControl(SET_CONTROL_RTS_OFF)
+
+ def setDTR(self, level=True):
+ """Set terminal status line: Data Terminal Ready."""
+ if not self._isOpen: raise portNotOpenError
+ if self.logger:
+ self.logger.info('set DTR to %s' % ('inactive', 'active')[bool(level)])
+ if level:
+ self.rfc2217SetControl(SET_CONTROL_DTR_ON)
+ else:
+ self.rfc2217SetControl(SET_CONTROL_DTR_OFF)
+
+ def getCTS(self):
+ """Read terminal status line: Clear To Send."""
+ if not self._isOpen: raise portNotOpenError
+ return bool(self.getModemState() & MODEMSTATE_MASK_CTS)
+
+ def getDSR(self):
+ """Read terminal status line: Data Set Ready."""
+ if not self._isOpen: raise portNotOpenError
+ return bool(self.getModemState() & MODEMSTATE_MASK_DSR)
+
+ def getRI(self):
+ """Read terminal status line: Ring Indicator."""
+ if not self._isOpen: raise portNotOpenError
+ return bool(self.getModemState() & MODEMSTATE_MASK_RI)
+
+ def getCD(self):
+ """Read terminal status line: Carrier Detect."""
+ if not self._isOpen: raise portNotOpenError
+ return bool(self.getModemState() & MODEMSTATE_MASK_CD)
+
+ # - - - platform specific - - -
+ # None so far
+
+ # - - - RFC2217 specific - - -
+
+ def _telnetReadLoop(self):
+ """Read loop for the socket."""
+ mode = M_NORMAL
+ suboption = None
+ try:
+ while self._socket is not None:
+ try:
+ data = self._socket.recv(1024)
+ except socket.timeout:
+ # just need to get out of recv form time to time to check if
+ # still alive
+ continue
+ except socket.error, e:
+ # connection fails -> terminate loop
+ if self.logger:
+ self.logger.debug("socket error in reader thread: %s" % (e,))
+ break
+ if not data: break # lost connection
+ for byte in data:
+ if mode == M_NORMAL:
+ # interpret as command or as data
+ if byte == IAC:
+ mode = M_IAC_SEEN
+ else:
+ # store data in read buffer or sub option buffer
+ # depending on state
+ if suboption is not None:
+ suboption.append(byte)
+ else:
+ self._read_buffer.put(byte)
+ elif mode == M_IAC_SEEN:
+ if byte == IAC:
+ # interpret as command doubled -> insert character
+ # itself
+ if suboption is not None:
+ suboption.append(IAC)
+ else:
+ self._read_buffer.put(IAC)
+ mode = M_NORMAL
+ elif byte == SB:
+ # sub option start
+ suboption = bytearray()
+ mode = M_NORMAL
+ elif byte == SE:
+ # sub option end -> process it now
+ self._telnetProcessSubnegotiation(bytes(suboption))
+ suboption = None
+ mode = M_NORMAL
+ elif byte in (DO, DONT, WILL, WONT):
+ # negotiation
+ telnet_command = byte
+ mode = M_NEGOTIATE
+ else:
+ # other telnet commands
+ self._telnetProcessCommand(byte)
+ mode = M_NORMAL
+ elif mode == M_NEGOTIATE: # DO, DONT, WILL, WONT was received, option now following
+ self._telnetNegotiateOption(telnet_command, byte)
+ mode = M_NORMAL
+ finally:
+ self._thread = None
+ if self.logger:
+ self.logger.debug("read thread terminated")
+
+ # - incoming telnet commands and options
+
+ def _telnetProcessCommand(self, command):
+ """Process commands other than DO, DONT, WILL, WONT."""
+ # Currently none. RFC2217 only uses negotiation and subnegotiation.
+ if self.logger:
+ self.logger.warning("ignoring Telnet command: %r" % (command,))
+
+ def _telnetNegotiateOption(self, command, option):
+ """Process incoming DO, DONT, WILL, WONT."""
+ # check our registered telnet options and forward command to them
+ # they know themselves if they have to answer or not
+ known = False
+ for item in self._telnet_options:
+ # can have more than one match! as some options are duplicated for
+ # 'us' and 'them'
+ if item.option == option:
+ item.process_incoming(command)
+ known = True
+ if not known:
+ # handle unknown options
+ # only answer to positive requests and deny them
+ if command == WILL or command == DO:
+ self.telnetSendOption((command == WILL and DONT or WONT), option)
+ if self.logger:
+ self.logger.warning("rejected Telnet option: %r" % (option,))
+
+
+ def _telnetProcessSubnegotiation(self, suboption):
+ """Process subnegotiation, the data between IAC SB and IAC SE."""
+ if suboption[0:1] == COM_PORT_OPTION:
+ if suboption[1:2] == SERVER_NOTIFY_LINESTATE and len(suboption) >= 3:
+ self._linestate = ord(suboption[2:3]) # ensure it is a number
+ if self.logger:
+ self.logger.info("NOTIFY_LINESTATE: %s" % self._linestate)
+ elif suboption[1:2] == SERVER_NOTIFY_MODEMSTATE and len(suboption) >= 3:
+ self._modemstate = ord(suboption[2:3]) # ensure it is a number
+ if self.logger:
+ self.logger.info("NOTIFY_MODEMSTATE: %s" % self._modemstate)
+ # update time when we think that a poll would make sense
+ self._modemstate_expires = time.time() + 0.3
+ elif suboption[1:2] == FLOWCONTROL_SUSPEND:
+ self._remote_suspend_flow = True
+ elif suboption[1:2] == FLOWCONTROL_RESUME:
+ self._remote_suspend_flow = False
+ else:
+ for item in self._rfc2217_options.values():
+ if item.ack_option == suboption[1:2]:
+ #~ print "processing COM_PORT_OPTION: %r" % list(suboption[1:])
+ item.checkAnswer(bytes(suboption[2:]))
+ break
+ else:
+ if self.logger:
+ self.logger.warning("ignoring COM_PORT_OPTION: %r" % (suboption,))
+ else:
+ if self.logger:
+ self.logger.warning("ignoring subnegotiation: %r" % (suboption,))
+
+ # - outgoing telnet commands and options
+
+ def _internal_raw_write(self, data):
+ """internal socket write with no data escaping. used to send telnet stuff."""
+ self._write_lock.acquire()
+ try:
+ self._socket.sendall(data)
+ finally:
+ self._write_lock.release()
+
+ def telnetSendOption(self, action, option):
+ """Send DO, DONT, WILL, WONT."""
+ self._internal_raw_write(to_bytes([IAC, action, option]))
+
+ def rfc2217SendSubnegotiation(self, option, value=''):
+ """Subnegotiation of RFC2217 parameters."""
+ value = value.replace(IAC, IAC_DOUBLED)
+ self._internal_raw_write(to_bytes([IAC, SB, COM_PORT_OPTION, option] + list(value) + [IAC, SE]))
+
+ def rfc2217SendPurge(self, value):
+ item = self._rfc2217_options['purge']
+ item.set(value) # transmit desired purge type
+ item.wait(self._network_timeout) # wait for acknowledge from the server
+
+ def rfc2217SetControl(self, value):
+ item = self._rfc2217_options['control']
+ item.set(value) # transmit desired control type
+ if self._ignore_set_control_answer:
+ # answers are ignored when option is set. compatibility mode for
+ # servers that answer, but not the expected one... (or no answer
+ # at all) i.e. sredird
+ time.sleep(0.1) # this helps getting the unit tests passed
+ else:
+ item.wait(self._network_timeout) # wait for acknowledge from the server
+
+ def rfc2217FlowServerReady(self):
+ """\
+ check if server is ready to receive data. block for some time when
+ not.
+ """
+ #~ if self._remote_suspend_flow:
+ #~ wait---
+
+ def getModemState(self):
+ """\
+ get last modem state (cached value. If value is "old", request a new
+ one. This cache helps that we don't issue to many requests when e.g. all
+ status lines, one after the other is queried by the user (getCTS, getDSR
+ etc.)
+ """
+ # active modem state polling enabled? is the value fresh enough?
+ if self._poll_modem_state and self._modemstate_expires < time.time():
+ if self.logger:
+ self.logger.debug('polling modem state')
+ # when it is older, request an update
+ self.rfc2217SendSubnegotiation(NOTIFY_MODEMSTATE)
+ timeout_time = time.time() + self._network_timeout
+ while time.time() < timeout_time:
+ time.sleep(0.05) # prevent 100% CPU load
+ # when expiration time is updated, it means that there is a new
+ # value
+ if self._modemstate_expires > time.time():
+ if self.logger:
+ self.logger.warning('poll for modem state failed')
+ break
+ # even when there is a timeout, do not generate an error just
+ # return the last known value. this way we can support buggy
+ # servers that do not respond to polls, but send automatic
+ # updates.
+ if self._modemstate is not None:
+ if self.logger:
+ self.logger.debug('using cached modem state')
+ return self._modemstate
+ else:
+ # never received a notification from the server
+ raise SerialException("remote sends no NOTIFY_MODEMSTATE")
+
+
+# assemble Serial class with the platform specific implementation and the base
+# for file-like behavior. for Python 2.6 and newer, that provide the new I/O
+# library, derive from io.RawIOBase
+try:
+ import io
+except ImportError:
+ # classic version with our own file-like emulation
+ class Serial(RFC2217Serial, FileLike):
+ pass
+else:
+ # io library present
+ class Serial(RFC2217Serial, io.RawIOBase):
+ pass
+
+
+#############################################################################
+# The following is code that helps implementing an RFC 2217 server.
+
+class PortManager(object):
+ """\
+ This class manages the state of Telnet and RFC 2217. It needs a serial
+ instance and a connection to work with. Connection is expected to implement
+ a (thread safe) write function, that writes the string to the network.
+ """
+
+ def __init__(self, serial_port, connection, logger=None):
+ self.serial = serial_port
+ self.connection = connection
+ self.logger = logger
+ self._client_is_rfc2217 = False
+
+ # filter state machine
+ self.mode = M_NORMAL
+ self.suboption = None
+ self.telnet_command = None
+
+ # states for modem/line control events
+ self.modemstate_mask = 255
+ self.last_modemstate = None
+ self.linstate_mask = 0
+
+ # all supported telnet options
+ self._telnet_options = [
+ TelnetOption(self, 'ECHO', ECHO, WILL, WONT, DO, DONT, REQUESTED),
+ TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED),
+ TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, INACTIVE),
+ TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE),
+ TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, REQUESTED),
+ TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED, self._client_ok),
+ TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, INACTIVE, self._client_ok),
+ ]
+
+ # negotiate Telnet/RFC2217 -> send initial requests
+ if self.logger:
+ self.logger.debug("requesting initial Telnet/RFC 2217 options")
+ for option in self._telnet_options:
+ if option.state is REQUESTED:
+ self.telnetSendOption(option.send_yes, option.option)
+ # issue 1st modem state notification
+
+ def _client_ok(self):
+ """\
+ callback of telnet option. It gets called when option is activated.
+ This one here is used to detect when the client agrees on RFC 2217. A
+ flag is set so that other functions like check_modem_lines know if the
+ client is OK.
+ """
+ # The callback is used for we and they so if one party agrees, we're
+ # already happy. it seems not all servers do the negotiation correctly
+ # and i guess there are incorrect clients too.. so be happy if client
+ # answers one or the other positively.
+ self._client_is_rfc2217 = True
+ if self.logger:
+ self.logger.info("client accepts RFC 2217")
+ # this is to ensure that the client gets a notification, even if there
+ # was no change
+ self.check_modem_lines(force_notification=True)
+
+ # - outgoing telnet commands and options
+
+ def telnetSendOption(self, action, option):
+ """Send DO, DONT, WILL, WONT."""
+ self.connection.write(to_bytes([IAC, action, option]))
+
+ def rfc2217SendSubnegotiation(self, option, value=''):
+ """Subnegotiation of RFC 2217 parameters."""
+ value = value.replace(IAC, IAC_DOUBLED)
+ self.connection.write(to_bytes([IAC, SB, COM_PORT_OPTION, option] + list(value) + [IAC, SE]))
+
+ # - check modem lines, needs to be called periodically from user to
+ # establish polling
+
+ def check_modem_lines(self, force_notification=False):
+ modemstate = (
+ (self.serial.getCTS() and MODEMSTATE_MASK_CTS) |
+ (self.serial.getDSR() and MODEMSTATE_MASK_DSR) |
+ (self.serial.getRI() and MODEMSTATE_MASK_RI) |
+ (self.serial.getCD() and MODEMSTATE_MASK_CD)
+ )
+ # check what has changed
+ deltas = modemstate ^ (self.last_modemstate or 0) # when last is None -> 0
+ if deltas & MODEMSTATE_MASK_CTS:
+ modemstate |= MODEMSTATE_MASK_CTS_CHANGE
+ if deltas & MODEMSTATE_MASK_DSR:
+ modemstate |= MODEMSTATE_MASK_DSR_CHANGE
+ if deltas & MODEMSTATE_MASK_RI:
+ modemstate |= MODEMSTATE_MASK_RI_CHANGE
+ if deltas & MODEMSTATE_MASK_CD:
+ modemstate |= MODEMSTATE_MASK_CD_CHANGE
+ # if new state is different and the mask allows this change, send
+ # notification. suppress notifications when client is not rfc2217
+ if modemstate != self.last_modemstate or force_notification:
+ if (self._client_is_rfc2217 and (modemstate & self.modemstate_mask)) or force_notification:
+ self.rfc2217SendSubnegotiation(
+ SERVER_NOTIFY_MODEMSTATE,
+ to_bytes([modemstate & self.modemstate_mask])
+ )
+ if self.logger:
+ self.logger.info("NOTIFY_MODEMSTATE: %s" % (modemstate,))
+ # save last state, but forget about deltas.
+ # otherwise it would also notify about changing deltas which is
+ # probably not very useful
+ self.last_modemstate = modemstate & 0xf0
+
+ # - outgoing data escaping
+
+ def escape(self, data):
+ """\
+ This generator function is for the user. All outgoing data has to be
+ properly escaped, so that no IAC character in the data stream messes up
+ the Telnet state machine in the server.
+
+ socket.sendall(escape(data))
+ """
+ for byte in data:
+ if byte == IAC:
+ yield IAC
+ yield IAC
+ else:
+ yield byte
+
+ # - incoming data filter
+
+ def filter(self, data):
+ """\
+ Handle a bunch of incoming bytes. This is a generator. It will yield
+ all characters not of interest for Telnet/RFC 2217.
+
+ The idea is that the reader thread pushes data from the socket through
+ this filter:
+
+ for byte in filter(socket.recv(1024)):
+ # do things like CR/LF conversion/whatever
+ # and write data to the serial port
+ serial.write(byte)
+
+ (socket error handling code left as exercise for the reader)
+ """
+ for byte in data:
+ if self.mode == M_NORMAL:
+ # interpret as command or as data
+ if byte == IAC:
+ self.mode = M_IAC_SEEN
+ else:
+ # store data in sub option buffer or pass it to our
+ # consumer depending on state
+ if self.suboption is not None:
+ self.suboption.append(byte)
+ else:
+ yield byte
+ elif self.mode == M_IAC_SEEN:
+ if byte == IAC:
+ # interpret as command doubled -> insert character
+ # itself
+ if self.suboption is not None:
+ self.suboption.append(byte)
+ else:
+ yield byte
+ self.mode = M_NORMAL
+ elif byte == SB:
+ # sub option start
+ self.suboption = bytearray()
+ self.mode = M_NORMAL
+ elif byte == SE:
+ # sub option end -> process it now
+ self._telnetProcessSubnegotiation(bytes(self.suboption))
+ self.suboption = None
+ self.mode = M_NORMAL
+ elif byte in (DO, DONT, WILL, WONT):
+ # negotiation
+ self.telnet_command = byte
+ self.mode = M_NEGOTIATE
+ else:
+ # other telnet commands
+ self._telnetProcessCommand(byte)
+ self.mode = M_NORMAL
+ elif self.mode == M_NEGOTIATE: # DO, DONT, WILL, WONT was received, option now following
+ self._telnetNegotiateOption(self.telnet_command, byte)
+ self.mode = M_NORMAL
+
+ # - incoming telnet commands and options
+
+ def _telnetProcessCommand(self, command):
+ """Process commands other than DO, DONT, WILL, WONT."""
+ # Currently none. RFC2217 only uses negotiation and subnegotiation.
+ if self.logger:
+ self.logger.warning("ignoring Telnet command: %r" % (command,))
+
+ def _telnetNegotiateOption(self, command, option):
+ """Process incoming DO, DONT, WILL, WONT."""
+ # check our registered telnet options and forward command to them
+ # they know themselves if they have to answer or not
+ known = False
+ for item in self._telnet_options:
+ # can have more than one match! as some options are duplicated for
+ # 'us' and 'them'
+ if item.option == option:
+ item.process_incoming(command)
+ known = True
+ if not known:
+ # handle unknown options
+ # only answer to positive requests and deny them
+ if command == WILL or command == DO:
+ self.telnetSendOption((command == WILL and DONT or WONT), option)
+ if self.logger:
+ self.logger.warning("rejected Telnet option: %r" % (option,))
+
+
+ def _telnetProcessSubnegotiation(self, suboption):
+ """Process subnegotiation, the data between IAC SB and IAC SE."""
+ if suboption[0:1] == COM_PORT_OPTION:
+ if self.logger:
+ self.logger.debug('received COM_PORT_OPTION: %r' % (suboption,))
+ if suboption[1:2] == SET_BAUDRATE:
+ backup = self.serial.baudrate
+ try:
+ (baudrate,) = struct.unpack("!I", suboption[2:6])
+ if baudrate != 0:
+ self.serial.baudrate = baudrate
+ except ValueError, e:
+ if self.logger:
+ self.logger.error("failed to set baud rate: %s" % (e,))
+ self.serial.baudrate = backup
+ else:
+ if self.logger:
+ self.logger.info("%s baud rate: %s" % (baudrate and 'set' or 'get', self.serial.baudrate))
+ self.rfc2217SendSubnegotiation(SERVER_SET_BAUDRATE, struct.pack("!I", self.serial.baudrate))
+ elif suboption[1:2] == SET_DATASIZE:
+ backup = self.serial.bytesize
+ try:
+ (datasize,) = struct.unpack("!B", suboption[2:3])
+ if datasize != 0:
+ self.serial.bytesize = datasize
+ except ValueError, e:
+ if self.logger:
+ self.logger.error("failed to set data size: %s" % (e,))
+ self.serial.bytesize = backup
+ else:
+ if self.logger:
+ self.logger.info("%s data size: %s" % (datasize and 'set' or 'get', self.serial.bytesize))
+ self.rfc2217SendSubnegotiation(SERVER_SET_DATASIZE, struct.pack("!B", self.serial.bytesize))
+ elif suboption[1:2] == SET_PARITY:
+ backup = self.serial.parity
+ try:
+ parity = struct.unpack("!B", suboption[2:3])[0]
+ if parity != 0:
+ self.serial.parity = RFC2217_REVERSE_PARITY_MAP[parity]
+ except ValueError, e:
+ if self.logger:
+ self.logger.error("failed to set parity: %s" % (e,))
+ self.serial.parity = backup
+ else:
+ if self.logger:
+ self.logger.info("%s parity: %s" % (parity and 'set' or 'get', self.serial.parity))
+ self.rfc2217SendSubnegotiation(
+ SERVER_SET_PARITY,
+ struct.pack("!B", RFC2217_PARITY_MAP[self.serial.parity])
+ )
+ elif suboption[1:2] == SET_STOPSIZE:
+ backup = self.serial.stopbits
+ try:
+ stopbits = struct.unpack("!B", suboption[2:3])[0]
+ if stopbits != 0:
+ self.serial.stopbits = RFC2217_REVERSE_STOPBIT_MAP[stopbits]
+ except ValueError, e:
+ if self.logger:
+ self.logger.error("failed to set stop bits: %s" % (e,))
+ self.serial.stopbits = backup
+ else:
+ if self.logger:
+ self.logger.info("%s stop bits: %s" % (stopbits and 'set' or 'get', self.serial.stopbits))
+ self.rfc2217SendSubnegotiation(
+ SERVER_SET_STOPSIZE,
+ struct.pack("!B", RFC2217_STOPBIT_MAP[self.serial.stopbits])
+ )
+ elif suboption[1:2] == SET_CONTROL:
+ if suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING:
+ if self.serial.xonxoff:
+ self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL)
+ elif self.serial.rtscts:
+ self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL)
+ else:
+ self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL)
+ elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL:
+ self.serial.xonxoff = False
+ self.serial.rtscts = False
+ if self.logger:
+ self.logger.info("changed flow control to None")
+ self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL)
+ elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTROL:
+ self.serial.xonxoff = True
+ if self.logger:
+ self.logger.info("changed flow control to XON/XOFF")
+ self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL)
+ elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTROL:
+ self.serial.rtscts = True
+ if self.logger:
+ self.logger.info("changed flow control to RTS/CTS")
+ self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL)
+ elif suboption[2:3] == SET_CONTROL_REQ_BREAK_STATE:
+ if self.logger:
+ self.logger.warning("requested break state - not implemented")
+ pass # XXX needs cached value
+ elif suboption[2:3] == SET_CONTROL_BREAK_ON:
+ self.serial.setBreak(True)
+ if self.logger:
+ self.logger.info("changed BREAK to active")
+ self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_ON)
+ elif suboption[2:3] == SET_CONTROL_BREAK_OFF:
+ self.serial.setBreak(False)
+ if self.logger:
+ self.logger.info("changed BREAK to inactive")
+ self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_OFF)
+ elif suboption[2:3] == SET_CONTROL_REQ_DTR:
+ if self.logger:
+ self.logger.warning("requested DTR state - not implemented")
+ pass # XXX needs cached value
+ elif suboption[2:3] == SET_CONTROL_DTR_ON:
+ self.serial.setDTR(True)
+ if self.logger:
+ self.logger.info("changed DTR to active")
+ self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_ON)
+ elif suboption[2:3] == SET_CONTROL_DTR_OFF:
+ self.serial.setDTR(False)
+ if self.logger:
+ self.logger.info("changed DTR to inactive")
+ self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_OFF)
+ elif suboption[2:3] == SET_CONTROL_REQ_RTS:
+ if self.logger:
+ self.logger.warning("requested RTS state - not implemented")
+ pass # XXX needs cached value
+ #~ self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON)
+ elif suboption[2:3] == SET_CONTROL_RTS_ON:
+ self.serial.setRTS(True)
+ if self.logger:
+ self.logger.info("changed RTS to active")
+ self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON)
+ elif suboption[2:3] == SET_CONTROL_RTS_OFF:
+ self.serial.setRTS(False)
+ if self.logger:
+ self.logger.info("changed RTS to inactive")
+ self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_OFF)
+ #~ elif suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING_IN:
+ #~ elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL_IN:
+ #~ elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTOL_IN:
+ #~ elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTOL_IN:
+ #~ elif suboption[2:3] == SET_CONTROL_USE_DCD_FLOW_CONTROL:
+ #~ elif suboption[2:3] == SET_CONTROL_USE_DTR_FLOW_CONTROL:
+ #~ elif suboption[2:3] == SET_CONTROL_USE_DSR_FLOW_CONTROL:
+ elif suboption[1:2] == NOTIFY_LINESTATE:
+ # client polls for current state
+ self.rfc2217SendSubnegotiation(
+ SERVER_NOTIFY_LINESTATE,
+ to_bytes([0]) # sorry, nothing like that implemented
+ )
+ elif suboption[1:2] == NOTIFY_MODEMSTATE:
+ if self.logger:
+ self.logger.info("request for modem state")
+ # client polls for current state
+ self.check_modem_lines(force_notification=True)
+ elif suboption[1:2] == FLOWCONTROL_SUSPEND:
+ if self.logger:
+ self.logger.info("suspend")
+ self._remote_suspend_flow = True
+ elif suboption[1:2] == FLOWCONTROL_RESUME:
+ if self.logger:
+ self.logger.info("resume")
+ self._remote_suspend_flow = False
+ elif suboption[1:2] == SET_LINESTATE_MASK:
+ self.linstate_mask = ord(suboption[2:3]) # ensure it is a number
+ if self.logger:
+ self.logger.info("line state mask: 0x%02x" % (self.linstate_mask,))
+ elif suboption[1:2] == SET_MODEMSTATE_MASK:
+ self.modemstate_mask = ord(suboption[2:3]) # ensure it is a number
+ if self.logger:
+ self.logger.info("modem state mask: 0x%02x" % (self.modemstate_mask,))
+ elif suboption[1:2] == PURGE_DATA:
+ if suboption[2:3] == PURGE_RECEIVE_BUFFER:
+ self.serial.flushInput()
+ if self.logger:
+ self.logger.info("purge in")
+ self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_RECEIVE_BUFFER)
+ elif suboption[2:3] == PURGE_TRANSMIT_BUFFER:
+ self.serial.flushOutput()
+ if self.logger:
+ self.logger.info("purge out")
+ self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_TRANSMIT_BUFFER)
+ elif suboption[2:3] == PURGE_BOTH_BUFFERS:
+ self.serial.flushInput()
+ self.serial.flushOutput()
+ if self.logger:
+ self.logger.info("purge both")
+ self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_BOTH_BUFFERS)
+ else:
+ if self.logger:
+ self.logger.error("undefined PURGE_DATA: %r" % list(suboption[2:]))
+ else:
+ if self.logger:
+ self.logger.error("undefined COM_PORT_OPTION: %r" % list(suboption[1:]))
+ else:
+ if self.logger:
+ self.logger.warning("unknown subnegotiation: %r" % (suboption,))
+
+
+# simple client test
+if __name__ == '__main__':
+ import sys
+ s = Serial('rfc2217://localhost:7000', 115200)
+ sys.stdout.write('%s\n' % s)
+
+ #~ s.baudrate = 1898
+
+ sys.stdout.write("write...\n")
+ s.write("hello\n")
+ s.flush()
+ sys.stdout.write("read: %s\n" % s.read(5))
+
+ #~ s.baudrate = 19200
+ #~ s.databits = 7
+ s.close()
diff --git a/scripts/serial/serialcli.py b/scripts/serial/serialcli.py
new file mode 100644
index 0000000..9ab3876
--- /dev/null
+++ b/scripts/serial/serialcli.py
@@ -0,0 +1,284 @@
+#! python
+# Python Serial Port Extension for Win32, Linux, BSD, Jython and .NET/Mono
+# serial driver for .NET/Mono (IronPython), .NET >= 2
+# see __init__.py
+#
+# (C) 2008 Chris Liechti <cliechti@gmx.net>
+# this is distributed under a free software license, see license.txt
+
+import clr
+import System
+import System.IO.Ports
+from serial.serialutil import *
+
+
+def device(portnum):
+ """Turn a port number into a device name"""
+ return System.IO.Ports.SerialPort.GetPortNames()[portnum]
+
+
+# must invoke function with byte array, make a helper to convert strings
+# to byte arrays
+sab = System.Array[System.Byte]
+def as_byte_array(string):
+ return sab([ord(x) for x in string]) # XXX will require adaption when run with a 3.x compatible IronPython
+
+class IronSerial(SerialBase):
+ """Serial port implementation for .NET/Mono."""
+
+ BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
+ 9600, 19200, 38400, 57600, 115200)
+
+ def open(self):
+ """\
+ Open port with current settings. This may throw a SerialException
+ if the port cannot be opened.
+ """
+ if self._port is None:
+ raise SerialException("Port must be configured before it can be used.")
+ if self._isOpen:
+ raise SerialException("Port is already open.")
+ try:
+ self._port_handle = System.IO.Ports.SerialPort(self.portstr)
+ except Exception, msg:
+ self._port_handle = None
+ raise SerialException("could not open port %s: %s" % (self.portstr, msg))
+
+ self._reconfigurePort()
+ self._port_handle.Open()
+ self._isOpen = True
+ if not self._rtscts:
+ self.setRTS(True)
+ self.setDTR(True)
+ self.flushInput()
+ self.flushOutput()
+
+ def _reconfigurePort(self):
+ """Set communication parameters on opened port."""
+ if not self._port_handle:
+ raise SerialException("Can only operate on a valid port handle")
+
+ #~ self._port_handle.ReceivedBytesThreshold = 1
+
+ if self._timeout is None:
+ self._port_handle.ReadTimeout = System.IO.Ports.SerialPort.InfiniteTimeout
+ else:
+ self._port_handle.ReadTimeout = int(self._timeout*1000)
+
+ # if self._timeout != 0 and self._interCharTimeout is not None:
+ # timeouts = (int(self._interCharTimeout * 1000),) + timeouts[1:]
+
+ if self._writeTimeout is None:
+ self._port_handle.WriteTimeout = System.IO.Ports.SerialPort.InfiniteTimeout
+ else:
+ self._port_handle.WriteTimeout = int(self._writeTimeout*1000)
+
+
+ # Setup the connection info.
+ try:
+ self._port_handle.BaudRate = self._baudrate
+ except IOError, e:
+ # catch errors from illegal baudrate settings
+ raise ValueError(str(e))
+
+ if self._bytesize == FIVEBITS:
+ self._port_handle.DataBits = 5
+ elif self._bytesize == SIXBITS:
+ self._port_handle.DataBits = 6
+ elif self._bytesize == SEVENBITS:
+ self._port_handle.DataBits = 7
+ elif self._bytesize == EIGHTBITS:
+ self._port_handle.DataBits = 8
+ else:
+ raise ValueError("Unsupported number of data bits: %r" % self._bytesize)
+
+ if self._parity == PARITY_NONE:
+ self._port_handle.Parity = getattr(System.IO.Ports.Parity, 'None') # reserved keyword in Py3k
+ elif self._parity == PARITY_EVEN:
+ self._port_handle.Parity = System.IO.Ports.Parity.Even
+ elif self._parity == PARITY_ODD:
+ self._port_handle.Parity = System.IO.Ports.Parity.Odd
+ elif self._parity == PARITY_MARK:
+ self._port_handle.Parity = System.IO.Ports.Parity.Mark
+ elif self._parity == PARITY_SPACE:
+ self._port_handle.Parity = System.IO.Ports.Parity.Space
+ else:
+ raise ValueError("Unsupported parity mode: %r" % self._parity)
+
+ if self._stopbits == STOPBITS_ONE:
+ self._port_handle.StopBits = System.IO.Ports.StopBits.One
+ elif self._stopbits == STOPBITS_ONE_POINT_FIVE:
+ self._port_handle.StopBits = System.IO.Ports.StopBits.OnePointFive
+ elif self._stopbits == STOPBITS_TWO:
+ self._port_handle.StopBits = System.IO.Ports.StopBits.Two
+ else:
+ raise ValueError("Unsupported number of stop bits: %r" % self._stopbits)
+
+ if self._rtscts and self._xonxoff:
+ self._port_handle.Handshake = System.IO.Ports.Handshake.RequestToSendXOnXOff
+ elif self._rtscts:
+ self._port_handle.Handshake = System.IO.Ports.Handshake.RequestToSend
+ elif self._xonxoff:
+ self._port_handle.Handshake = System.IO.Ports.Handshake.XOnXOff
+ else:
+ self._port_handle.Handshake = getattr(System.IO.Ports.Handshake, 'None') # reserved keyword in Py3k
+
+ #~ def __del__(self):
+ #~ self.close()
+
+ def close(self):
+ """Close port"""
+ if self._isOpen:
+ if self._port_handle:
+ try:
+ self._port_handle.Close()
+ except System.IO.Ports.InvalidOperationException:
+ # ignore errors. can happen for unplugged USB serial devices
+ pass
+ self._port_handle = None
+ self._isOpen = False
+
+ def makeDeviceName(self, port):
+ try:
+ return device(port)
+ except TypeError, e:
+ raise SerialException(str(e))
+
+ # - - - - - - - - - - - - - - - - - - - - - - - -
+
+ def inWaiting(self):
+ """Return the number of characters currently in the input buffer."""
+ if not self._port_handle: raise portNotOpenError
+ return self._port_handle.BytesToRead
+
+ def read(self, size=1):
+ """\
+ Read size bytes from the serial port. If a timeout is set it may
+ return less characters as requested. With no timeout it will block
+ until the requested number of bytes is read.
+ """
+ if not self._port_handle: raise portNotOpenError
+ # must use single byte reads as this is the only way to read
+ # without applying encodings
+ data = bytearray()
+ while size:
+ try:
+ data.append(self._port_handle.ReadByte())
+ except System.TimeoutException, e:
+ break
+ else:
+ size -= 1
+ return bytes(data)
+
+ def write(self, data):
+ """Output the given string over the serial port."""
+ if not self._port_handle: raise portNotOpenError
+ #~ if not isinstance(data, (bytes, bytearray)):
+ #~ raise TypeError('expected %s or bytearray, got %s' % (bytes, type(data)))
+ try:
+ # must call overloaded method with byte array argument
+ # as this is the only one not applying encodings
+ self._port_handle.Write(as_byte_array(data), 0, len(data))
+ except System.TimeoutException, e:
+ raise writeTimeoutError
+ return len(data)
+
+ def flushInput(self):
+ """Clear input buffer, discarding all that is in the buffer."""
+ if not self._port_handle: raise portNotOpenError
+ self._port_handle.DiscardInBuffer()
+
+ def flushOutput(self):
+ """\
+ Clear output buffer, aborting the current output and
+ discarding all that is in the buffer.
+ """
+ if not self._port_handle: raise portNotOpenError
+ self._port_handle.DiscardOutBuffer()
+
+ def sendBreak(self, duration=0.25):
+ """\
+ Send break condition. Timed, returns to idle state after given
+ duration.
+ """
+ if not self._port_handle: raise portNotOpenError
+ import time
+ self._port_handle.BreakState = True
+ time.sleep(duration)
+ self._port_handle.BreakState = False
+
+ def setBreak(self, level=True):
+ """
+ Set break: Controls TXD. When active, to transmitting is possible.
+ """
+ if not self._port_handle: raise portNotOpenError
+ self._port_handle.BreakState = bool(level)
+
+ def setRTS(self, level=True):
+ """Set terminal status line: Request To Send"""
+ if not self._port_handle: raise portNotOpenError
+ self._port_handle.RtsEnable = bool(level)
+
+ def setDTR(self, level=True):
+ """Set terminal status line: Data Terminal Ready"""
+ if not self._port_handle: raise portNotOpenError
+ self._port_handle.DtrEnable = bool(level)
+
+ def getCTS(self):
+ """Read terminal status line: Clear To Send"""
+ if not self._port_handle: raise portNotOpenError
+ return self._port_handle.CtsHolding
+
+ def getDSR(self):
+ """Read terminal status line: Data Set Ready"""
+ if not self._port_handle: raise portNotOpenError
+ return self._port_handle.DsrHolding
+
+ def getRI(self):
+ """Read terminal status line: Ring Indicator"""
+ if not self._port_handle: raise portNotOpenError
+ #~ return self._port_handle.XXX
+ return False #XXX an error would be better
+
+ def getCD(self):
+ """Read terminal status line: Carrier Detect"""
+ if not self._port_handle: raise portNotOpenError
+ return self._port_handle.CDHolding
+
+ # - - platform specific - - - -
+ # none
+
+
+# assemble Serial class with the platform specific implementation and the base
+# for file-like behavior. for Python 2.6 and newer, that provide the new I/O
+# library, derive from io.RawIOBase
+try:
+ import io
+except ImportError:
+ # classic version with our own file-like emulation
+ class Serial(IronSerial, FileLike):
+ pass
+else:
+ # io library present
+ class Serial(IronSerial, io.RawIOBase):
+ pass
+
+
+# Nur Testfunktion!!
+if __name__ == '__main__':
+ import sys
+
+ s = Serial(0)
+ sys.stdio.write('%s\n' % s)
+
+ s = Serial()
+ sys.stdio.write('%s\n' % s)
+
+
+ s.baudrate = 19200
+ s.databits = 7
+ s.close()
+ s.port = 0
+ s.open()
+ sys.stdio.write('%s\n' % s)
+
diff --git a/scripts/serial/serialposix.py b/scripts/serial/serialposix.py
new file mode 100644
index 0000000..359ad1b
--- /dev/null
+++ b/scripts/serial/serialposix.py
@@ -0,0 +1,730 @@
+#!/usr/bin/env python
+#
+# Python Serial Port Extension for Win32, Linux, BSD, Jython
+# module for serial IO for POSIX compatible systems, like Linux
+# see __init__.py
+#
+# (C) 2001-2010 Chris Liechti <cliechti@gmx.net>
+# this is distributed under a free software license, see license.txt
+#
+# parts based on code from Grant B. Edwards <grante@visi.com>:
+# ftp://ftp.visi.com/users/grante/python/PosixSerial.py
+#
+# references: http://www.easysw.com/~mike/serial/serial.html
+
+import sys, os, fcntl, termios, struct, select, errno, time
+from serial.serialutil import *
+
+# Do check the Python version as some constants have moved.
+if (sys.hexversion < 0x020100f0):
+ import TERMIOS
+else:
+ TERMIOS = termios
+
+if (sys.hexversion < 0x020200f0):
+ import FCNTL
+else:
+ FCNTL = fcntl
+
+# try to detect the OS so that a device can be selected...
+# this code block should supply a device() and set_special_baudrate() function
+# for the platform
+plat = sys.platform.lower()
+
+if plat[:5] == 'linux': # Linux (confirmed)
+
+ def device(port):
+ return '/dev/ttyS%d' % port
+
+ TCGETS2 = 0x802C542A
+ TCSETS2 = 0x402C542B
+ BOTHER = 0o010000
+
+ def set_special_baudrate(port, baudrate):
+ # right size is 44 on x86_64, allow for some growth
+ import array
+ buf = array.array('i', [0] * 64)
+
+ try:
+ # get serial_struct
+ FCNTL.ioctl(port.fd, TCGETS2, buf)
+ # set custom speed
+ buf[2] &= ~TERMIOS.CBAUD
+ buf[2] |= BOTHER
+ buf[9] = buf[10] = baudrate
+
+ # set serial_struct
+ res = FCNTL.ioctl(port.fd, TCSETS2, buf)
+ except IOError, e:
+ raise ValueError('Failed to set custom baud rate (%s): %s' % (baudrate, e))
+
+ baudrate_constants = {
+ 0: 0000000, # hang up
+ 50: 0000001,
+ 75: 0000002,
+ 110: 0000003,
+ 134: 0000004,
+ 150: 0000005,
+ 200: 0000006,
+ 300: 0000007,
+ 600: 0000010,
+ 1200: 0000011,
+ 1800: 0000012,
+ 2400: 0000013,
+ 4800: 0000014,
+ 9600: 0000015,
+ 19200: 0000016,
+ 38400: 0000017,
+ 57600: 0010001,
+ 115200: 0010002,
+ 230400: 0010003,
+ 460800: 0010004,
+ 500000: 0010005,
+ 576000: 0010006,
+ 921600: 0010007,
+ 1000000: 0010010,
+ 1152000: 0010011,
+ 1500000: 0010012,
+ 2000000: 0010013,
+ 2500000: 0010014,
+ 3000000: 0010015,
+ 3500000: 0010016,
+ 4000000: 0010017
+ }
+
+elif plat == 'cygwin': # cygwin/win32 (confirmed)
+
+ def device(port):
+ return '/dev/com%d' % (port + 1)
+
+ def set_special_baudrate(port, baudrate):
+ raise ValueError("sorry don't know how to handle non standard baud rate on this platform")
+
+ baudrate_constants = {
+ 128000: 0x01003,
+ 256000: 0x01005,
+ 500000: 0x01007,
+ 576000: 0x01008,
+ 921600: 0x01009,
+ 1000000: 0x0100a,
+ 1152000: 0x0100b,
+ 1500000: 0x0100c,
+ 2000000: 0x0100d,
+ 2500000: 0x0100e,
+ 3000000: 0x0100f
+ }
+
+elif plat[:7] == 'openbsd': # OpenBSD
+
+ def device(port):
+ return '/dev/cua%02d' % port
+
+ def set_special_baudrate(port, baudrate):
+ raise ValueError("sorry don't know how to handle non standard baud rate on this platform")
+
+ baudrate_constants = {}
+
+elif plat[:3] == 'bsd' or \
+ plat[:7] == 'freebsd':
+
+ def device(port):
+ return '/dev/cuad%d' % port
+
+ def set_special_baudrate(port, baudrate):
+ raise ValueError("sorry don't know how to handle non standard baud rate on this platform")
+
+ baudrate_constants = {}
+
+elif plat[:6] == 'darwin': # OS X
+
+ version = os.uname()[2].split('.')
+ # Tiger or above can support arbitrary serial speeds
+ if int(version[0]) >= 8:
+ def set_special_baudrate(port, baudrate):
+ # use IOKit-specific call to set up high speeds
+ import array, fcntl
+ buf = array.array('i', [baudrate])
+ IOSSIOSPEED = 0x80045402 #_IOW('T', 2, speed_t)
+ fcntl.ioctl(port.fd, IOSSIOSPEED, buf, 1)
+ else: # version < 8
+ def set_special_baudrate(port, baudrate):
+ raise ValueError("baud rate not supported")
+
+ def device(port):
+ return '/dev/cuad%d' % port
+
+ baudrate_constants = {}
+
+
+elif plat[:6] == 'netbsd': # NetBSD 1.6 testing by Erk
+
+ def device(port):
+ return '/dev/dty%02d' % port
+
+ def set_special_baudrate(port, baudrate):
+ raise ValueError("sorry don't know how to handle non standard baud rate on this platform")
+
+ baudrate_constants = {}
+
+elif plat[:4] == 'irix': # IRIX (partially tested)
+
+ def device(port):
+ return '/dev/ttyf%d' % (port+1) #XXX different device names depending on flow control
+
+ def set_special_baudrate(port, baudrate):
+ raise ValueError("sorry don't know how to handle non standard baud rate on this platform")
+
+ baudrate_constants = {}
+
+elif plat[:2] == 'hp': # HP-UX (not tested)
+
+ def device(port):
+ return '/dev/tty%dp0' % (port+1)
+
+ def set_special_baudrate(port, baudrate):
+ raise ValueError("sorry don't know how to handle non standard baud rate on this platform")
+
+ baudrate_constants = {}
+
+elif plat[:5] == 'sunos': # Solaris/SunOS (confirmed)
+
+ def device(port):
+ return '/dev/tty%c' % (ord('a')+port)
+
+ def set_special_baudrate(port, baudrate):
+ raise ValueError("sorry don't know how to handle non standard baud rate on this platform")
+
+ baudrate_constants = {}
+
+elif plat[:3] == 'aix': # AIX
+
+ def device(port):
+ return '/dev/tty%d' % (port)
+
+ def set_special_baudrate(port, baudrate):
+ raise ValueError("sorry don't know how to handle non standard baud rate on this platform")
+
+ baudrate_constants = {}
+
+else:
+ # platform detection has failed...
+ sys.stderr.write("""\
+don't know how to number ttys on this system.
+! Use an explicit path (eg /dev/ttyS1) or send this information to
+! the author of this module:
+
+sys.platform = %r
+os.name = %r
+serialposix.py version = %s
+
+also add the device name of the serial port and where the
+counting starts for the first serial port.
+e.g. 'first serial port: /dev/ttyS0'
+and with a bit luck you can get this module running...
+""" % (sys.platform, os.name, VERSION))
+ # no exception, just continue with a brave attempt to build a device name
+ # even if the device name is not correct for the platform it has chances
+ # to work using a string with the real device name as port parameter.
+ def device(portum):
+ return '/dev/ttyS%d' % portnum
+ def set_special_baudrate(port, baudrate):
+ raise SerialException("sorry don't know how to handle non standard baud rate on this platform")
+ baudrate_constants = {}
+ #~ raise Exception, "this module does not run on this platform, sorry."
+
+# whats up with "aix", "beos", ....
+# they should work, just need to know the device names.
+
+
+# load some constants for later use.
+# try to use values from TERMIOS, use defaults from linux otherwise
+TIOCMGET = hasattr(TERMIOS, 'TIOCMGET') and TERMIOS.TIOCMGET or 0x5415
+TIOCMBIS = hasattr(TERMIOS, 'TIOCMBIS') and TERMIOS.TIOCMBIS or 0x5416
+TIOCMBIC = hasattr(TERMIOS, 'TIOCMBIC') and TERMIOS.TIOCMBIC or 0x5417
+TIOCMSET = hasattr(TERMIOS, 'TIOCMSET') and TERMIOS.TIOCMSET or 0x5418
+
+#TIOCM_LE = hasattr(TERMIOS, 'TIOCM_LE') and TERMIOS.TIOCM_LE or 0x001
+TIOCM_DTR = hasattr(TERMIOS, 'TIOCM_DTR') and TERMIOS.TIOCM_DTR or 0x002
+TIOCM_RTS = hasattr(TERMIOS, 'TIOCM_RTS') and TERMIOS.TIOCM_RTS or 0x004
+#TIOCM_ST = hasattr(TERMIOS, 'TIOCM_ST') and TERMIOS.TIOCM_ST or 0x008
+#TIOCM_SR = hasattr(TERMIOS, 'TIOCM_SR') and TERMIOS.TIOCM_SR or 0x010
+
+TIOCM_CTS = hasattr(TERMIOS, 'TIOCM_CTS') and TERMIOS.TIOCM_CTS or 0x020
+TIOCM_CAR = hasattr(TERMIOS, 'TIOCM_CAR') and TERMIOS.TIOCM_CAR or 0x040
+TIOCM_RNG = hasattr(TERMIOS, 'TIOCM_RNG') and TERMIOS.TIOCM_RNG or 0x080
+TIOCM_DSR = hasattr(TERMIOS, 'TIOCM_DSR') and TERMIOS.TIOCM_DSR or 0x100
+TIOCM_CD = hasattr(TERMIOS, 'TIOCM_CD') and TERMIOS.TIOCM_CD or TIOCM_CAR
+TIOCM_RI = hasattr(TERMIOS, 'TIOCM_RI') and TERMIOS.TIOCM_RI or TIOCM_RNG
+#TIOCM_OUT1 = hasattr(TERMIOS, 'TIOCM_OUT1') and TERMIOS.TIOCM_OUT1 or 0x2000
+#TIOCM_OUT2 = hasattr(TERMIOS, 'TIOCM_OUT2') and TERMIOS.TIOCM_OUT2 or 0x4000
+if hasattr(TERMIOS, 'TIOCINQ'):
+ TIOCINQ = TERMIOS.TIOCINQ
+else:
+ TIOCINQ = hasattr(TERMIOS, 'FIONREAD') and TERMIOS.FIONREAD or 0x541B
+TIOCOUTQ = hasattr(TERMIOS, 'TIOCOUTQ') and TERMIOS.TIOCOUTQ or 0x5411
+
+TIOCM_zero_str = struct.pack('I', 0)
+TIOCM_RTS_str = struct.pack('I', TIOCM_RTS)
+TIOCM_DTR_str = struct.pack('I', TIOCM_DTR)
+
+TIOCSBRK = hasattr(TERMIOS, 'TIOCSBRK') and TERMIOS.TIOCSBRK or 0x5427
+TIOCCBRK = hasattr(TERMIOS, 'TIOCCBRK') and TERMIOS.TIOCCBRK or 0x5428
+
+CMSPAR = 010000000000 # Use "stick" (mark/space) parity
+
+
+class PosixSerial(SerialBase):
+ """\
+ Serial port class POSIX implementation. Serial port configuration is
+ done with termios and fcntl. Runs on Linux and many other Un*x like
+ systems.
+ """
+
+ def open(self):
+ """\
+ Open port with current settings. This may throw a SerialException
+ if the port cannot be opened."""
+ if self._port is None:
+ raise SerialException("Port must be configured before it can be used.")
+ if self._isOpen:
+ raise SerialException("Port is already open.")
+ self.fd = None
+ # open
+ try:
+ self.fd = os.open(self.portstr, os.O_RDWR|os.O_NOCTTY|os.O_NONBLOCK)
+ except OSError, msg:
+ self.fd = None
+ raise SerialException(msg.errno, "could not open port %s: %s" % (self._port, msg))
+ #~ fcntl.fcntl(self.fd, FCNTL.F_SETFL, 0) # set blocking
+
+ try:
+ self._reconfigurePort()
+ except:
+ try:
+ os.close(self.fd)
+ except:
+ # ignore any exception when closing the port
+ # also to keep original exception that happened when setting up
+ pass
+ self.fd = None
+ raise
+ else:
+ self._isOpen = True
+ self.flushInput()
+
+
+ def _reconfigurePort(self):
+ """Set communication parameters on opened port."""
+ if self.fd is None:
+ raise SerialException("Can only operate on a valid file descriptor")
+ custom_baud = None
+
+ vmin = vtime = 0 # timeout is done via select
+ if self._interCharTimeout is not None:
+ vmin = 1
+ vtime = int(self._interCharTimeout * 10)
+ try:
+ orig_attr = termios.tcgetattr(self.fd)
+ iflag, oflag, cflag, lflag, ispeed, ospeed, cc = orig_attr
+ except termios.error, msg: # if a port is nonexistent but has a /dev file, it'll fail here
+ raise SerialException("Could not configure port: %s" % msg)
+ # set up raw mode / no echo / binary
+ cflag |= (TERMIOS.CLOCAL|TERMIOS.CREAD)
+ lflag &= ~(TERMIOS.ICANON|TERMIOS.ECHO|TERMIOS.ECHOE|TERMIOS.ECHOK|TERMIOS.ECHONL|
+ TERMIOS.ISIG|TERMIOS.IEXTEN) #|TERMIOS.ECHOPRT
+ for flag in ('ECHOCTL', 'ECHOKE'): # netbsd workaround for Erk
+ if hasattr(TERMIOS, flag):
+ lflag &= ~getattr(TERMIOS, flag)
+
+ oflag &= ~(TERMIOS.OPOST)
+ iflag &= ~(TERMIOS.INLCR|TERMIOS.IGNCR|TERMIOS.ICRNL|TERMIOS.IGNBRK)
+ if hasattr(TERMIOS, 'IUCLC'):
+ iflag &= ~TERMIOS.IUCLC
+ if hasattr(TERMIOS, 'PARMRK'):
+ iflag &= ~TERMIOS.PARMRK
+
+ # setup baud rate
+ try:
+ ispeed = ospeed = getattr(TERMIOS, 'B%s' % (self._baudrate))
+ except AttributeError:
+ try:
+ ispeed = ospeed = baudrate_constants[self._baudrate]
+ except KeyError:
+ #~ raise ValueError('Invalid baud rate: %r' % self._baudrate)
+ # may need custom baud rate, it isn't in our list.
+ ispeed = ospeed = getattr(TERMIOS, 'B38400')
+ try:
+ custom_baud = int(self._baudrate) # store for later
+ except ValueError:
+ raise ValueError('Invalid baud rate: %r' % self._baudrate)
+ else:
+ if custom_baud < 0:
+ raise ValueError('Invalid baud rate: %r' % self._baudrate)
+
+ # setup char len
+ cflag &= ~TERMIOS.CSIZE
+ if self._bytesize == 8:
+ cflag |= TERMIOS.CS8
+ elif self._bytesize == 7:
+ cflag |= TERMIOS.CS7
+ elif self._bytesize == 6:
+ cflag |= TERMIOS.CS6
+ elif self._bytesize == 5:
+ cflag |= TERMIOS.CS5
+ else:
+ raise ValueError('Invalid char len: %r' % self._bytesize)
+ # setup stop bits
+ if self._stopbits == STOPBITS_ONE:
+ cflag &= ~(TERMIOS.CSTOPB)
+ elif self._stopbits == STOPBITS_ONE_POINT_FIVE:
+ cflag |= (TERMIOS.CSTOPB) # XXX same as TWO.. there is no POSIX support for 1.5
+ elif self._stopbits == STOPBITS_TWO:
+ cflag |= (TERMIOS.CSTOPB)
+ else:
+ raise ValueError('Invalid stop bit specification: %r' % self._stopbits)
+ # setup parity
+ iflag &= ~(TERMIOS.INPCK|TERMIOS.ISTRIP)
+ if self._parity == PARITY_NONE:
+ cflag &= ~(TERMIOS.PARENB|TERMIOS.PARODD)
+ elif self._parity == PARITY_EVEN:
+ cflag &= ~(TERMIOS.PARODD)
+ cflag |= (TERMIOS.PARENB)
+ elif self._parity == PARITY_ODD:
+ cflag |= (TERMIOS.PARENB|TERMIOS.PARODD)
+ elif self._parity == PARITY_MARK and plat[:5] == 'linux':
+ cflag |= (TERMIOS.PARENB|CMSPAR|TERMIOS.PARODD)
+ elif self._parity == PARITY_SPACE and plat[:5] == 'linux':
+ cflag |= (TERMIOS.PARENB|CMSPAR)
+ cflag &= ~(TERMIOS.PARODD)
+ else:
+ raise ValueError('Invalid parity: %r' % self._parity)
+ # setup flow control
+ # xonxoff
+ if hasattr(TERMIOS, 'IXANY'):
+ if self._xonxoff:
+ iflag |= (TERMIOS.IXON|TERMIOS.IXOFF) #|TERMIOS.IXANY)
+ else:
+ iflag &= ~(TERMIOS.IXON|TERMIOS.IXOFF|TERMIOS.IXANY)
+ else:
+ if self._xonxoff:
+ iflag |= (TERMIOS.IXON|TERMIOS.IXOFF)
+ else:
+ iflag &= ~(TERMIOS.IXON|TERMIOS.IXOFF)
+ # rtscts
+ if hasattr(TERMIOS, 'CRTSCTS'):
+ if self._rtscts:
+ cflag |= (TERMIOS.CRTSCTS)
+ else:
+ cflag &= ~(TERMIOS.CRTSCTS)
+ elif hasattr(TERMIOS, 'CNEW_RTSCTS'): # try it with alternate constant name
+ if self._rtscts:
+ cflag |= (TERMIOS.CNEW_RTSCTS)
+ else:
+ cflag &= ~(TERMIOS.CNEW_RTSCTS)
+ # XXX should there be a warning if setting up rtscts (and xonxoff etc) fails??
+
+ # buffer
+ # vmin "minimal number of characters to be read. 0 for non blocking"
+ if vmin < 0 or vmin > 255:
+ raise ValueError('Invalid vmin: %r ' % vmin)
+ cc[TERMIOS.VMIN] = vmin
+ # vtime
+ if vtime < 0 or vtime > 255:
+ raise ValueError('Invalid vtime: %r' % vtime)
+ cc[TERMIOS.VTIME] = vtime
+ # activate settings
+ if [iflag, oflag, cflag, lflag, ispeed, ospeed, cc] != orig_attr:
+ termios.tcsetattr(self.fd, TERMIOS.TCSANOW, [iflag, oflag, cflag, lflag, ispeed, ospeed, cc])
+
+ # apply custom baud rate, if any
+ if custom_baud is not None:
+ set_special_baudrate(self, custom_baud)
+
+ def close(self):
+ """Close port"""
+ if self._isOpen:
+ if self.fd is not None:
+ os.close(self.fd)
+ self.fd = None
+ self._isOpen = False
+
+ def makeDeviceName(self, port):
+ return device(port)
+
+ # - - - - - - - - - - - - - - - - - - - - - - - -
+
+ def inWaiting(self):
+ """Return the number of characters currently in the input buffer."""
+ #~ s = fcntl.ioctl(self.fd, TERMIOS.FIONREAD, TIOCM_zero_str)
+ s = fcntl.ioctl(self.fd, TIOCINQ, TIOCM_zero_str)
+ return struct.unpack('I',s)[0]
+
+ # select based implementation, proved to work on many systems
+ def read(self, size=1):
+ """\
+ Read size bytes from the serial port. If a timeout is set it may
+ return less characters as requested. With no timeout it will block
+ until the requested number of bytes is read.
+ """
+ if not self._isOpen: raise portNotOpenError
+ read = bytearray()
+ while len(read) < size:
+ try:
+ ready,_,_ = select.select([self.fd],[],[], self._timeout)
+ # If select was used with a timeout, and the timeout occurs, it
+ # returns with empty lists -> thus abort read operation.
+ # For timeout == 0 (non-blocking operation) also abort when there
+ # is nothing to read.
+ if not ready:
+ break # timeout
+ buf = os.read(self.fd, size-len(read))
+ # read should always return some data as select reported it was
+ # ready to read when we get to this point.
+ if not buf:
+ # Disconnected devices, at least on Linux, show the
+ # behavior that they are always ready to read immediately
+ # but reading returns nothing.
+ raise SerialException('device reports readiness to read but returned no data (device disconnected or multiple access on port?)')
+ read.extend(buf)
+ except OSError, e:
+ # this is for Python 3.x where select.error is a subclass of OSError
+ # ignore EAGAIN errors. all other errors are shown
+ if e.errno != errno.EAGAIN:
+ raise SerialException('read failed: %s' % (e,))
+ except select.error, e:
+ # this is for Python 2.x
+ # ignore EAGAIN errors. all other errors are shown
+ # see also http://www.python.org/dev/peps/pep-3151/#select
+ if e[0] != errno.EAGAIN:
+ raise SerialException('read failed: %s' % (e,))
+ return bytes(read)
+
+ def write(self, data):
+ """Output the given string over the serial port."""
+ if not self._isOpen: raise portNotOpenError
+ d = to_bytes(data)
+ tx_len = len(d)
+ if self._writeTimeout is not None and self._writeTimeout > 0:
+ timeout = time.time() + self._writeTimeout
+ else:
+ timeout = None
+ while tx_len > 0:
+ try:
+ n = os.write(self.fd, d)
+ if timeout:
+ # when timeout is set, use select to wait for being ready
+ # with the time left as timeout
+ timeleft = timeout - time.time()
+ if timeleft < 0:
+ raise writeTimeoutError
+ _, ready, _ = select.select([], [self.fd], [], timeleft)
+ if not ready:
+ raise writeTimeoutError
+ else:
+ # wait for write operation
+ _, ready, _ = select.select([], [self.fd], [], None)
+ if not ready:
+ raise SerialException('write failed (select)')
+ d = d[n:]
+ tx_len -= n
+ except OSError, v:
+ if v.errno != errno.EAGAIN:
+ raise SerialException('write failed: %s' % (v,))
+ return len(data)
+
+ def flush(self):
+ """\
+ Flush of file like objects. In this case, wait until all data
+ is written.
+ """
+ self.drainOutput()
+
+ def flushInput(self):
+ """Clear input buffer, discarding all that is in the buffer."""
+ if not self._isOpen: raise portNotOpenError
+ termios.tcflush(self.fd, TERMIOS.TCIFLUSH)
+
+ def flushOutput(self):
+ """\
+ Clear output buffer, aborting the current output and discarding all
+ that is in the buffer.
+ """
+ if not self._isOpen: raise portNotOpenError
+ termios.tcflush(self.fd, TERMIOS.TCOFLUSH)
+
+ def sendBreak(self, duration=0.25):
+ """\
+ Send break condition. Timed, returns to idle state after given
+ duration.
+ """
+ if not self._isOpen: raise portNotOpenError
+ termios.tcsendbreak(self.fd, int(duration/0.25))
+
+ def setBreak(self, level=1):
+ """\
+ Set break: Controls TXD. When active, no transmitting is possible.
+ """
+ if self.fd is None: raise portNotOpenError
+ if level:
+ fcntl.ioctl(self.fd, TIOCSBRK)
+ else:
+ fcntl.ioctl(self.fd, TIOCCBRK)
+
+ def setRTS(self, level=1):
+ """Set terminal status line: Request To Send"""
+ if not self._isOpen: raise portNotOpenError
+ if level:
+ fcntl.ioctl(self.fd, TIOCMBIS, TIOCM_RTS_str)
+ else:
+ fcntl.ioctl(self.fd, TIOCMBIC, TIOCM_RTS_str)
+
+ def setDTR(self, level=1):
+ """Set terminal status line: Data Terminal Ready"""
+ if not self._isOpen: raise portNotOpenError
+ if level:
+ fcntl.ioctl(self.fd, TIOCMBIS, TIOCM_DTR_str)
+ else:
+ fcntl.ioctl(self.fd, TIOCMBIC, TIOCM_DTR_str)
+
+ def getCTS(self):
+ """Read terminal status line: Clear To Send"""
+ if not self._isOpen: raise portNotOpenError
+ s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str)
+ return struct.unpack('I',s)[0] & TIOCM_CTS != 0
+
+ def getDSR(self):
+ """Read terminal status line: Data Set Ready"""
+ if not self._isOpen: raise portNotOpenError
+ s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str)
+ return struct.unpack('I',s)[0] & TIOCM_DSR != 0
+
+ def getRI(self):
+ """Read terminal status line: Ring Indicator"""
+ if not self._isOpen: raise portNotOpenError
+ s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str)
+ return struct.unpack('I',s)[0] & TIOCM_RI != 0
+
+ def getCD(self):
+ """Read terminal status line: Carrier Detect"""
+ if not self._isOpen: raise portNotOpenError
+ s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str)
+ return struct.unpack('I',s)[0] & TIOCM_CD != 0
+
+ # - - platform specific - - - -
+
+ def outWaiting(self):
+ """Return the number of characters currently in the output buffer."""
+ #~ s = fcntl.ioctl(self.fd, TERMIOS.FIONREAD, TIOCM_zero_str)
+ s = fcntl.ioctl(self.fd, TIOCOUTQ, TIOCM_zero_str)
+ return struct.unpack('I',s)[0]
+
+ def drainOutput(self):
+ """internal - not portable!"""
+ if not self._isOpen: raise portNotOpenError
+ termios.tcdrain(self.fd)
+
+ def nonblocking(self):
+ """internal - not portable!"""
+ if not self._isOpen: raise portNotOpenError
+ fcntl.fcntl(self.fd, FCNTL.F_SETFL, os.O_NONBLOCK)
+
+ def fileno(self):
+ """\
+ For easier use of the serial port instance with select.
+ WARNING: this function is not portable to different platforms!
+ """
+ if not self._isOpen: raise portNotOpenError
+ return self.fd
+
+ def setXON(self, level=True):
+ """\
+ Manually control flow - when software flow control is enabled.
+ This will send XON (true) and XOFF (false) to the other device.
+ WARNING: this function is not portable to different platforms!
+ """
+ if not self.hComPort: raise portNotOpenError
+ if enable:
+ termios.tcflow(self.fd, TERMIOS.TCION)
+ else:
+ termios.tcflow(self.fd, TERMIOS.TCIOFF)
+
+ def flowControlOut(self, enable):
+ """\
+ Manually control flow of outgoing data - when hardware or software flow
+ control is enabled.
+ WARNING: this function is not portable to different platforms!
+ """
+ if not self._isOpen: raise portNotOpenError
+ if enable:
+ termios.tcflow(self.fd, TERMIOS.TCOON)
+ else:
+ termios.tcflow(self.fd, TERMIOS.TCOOFF)
+
+
+# assemble Serial class with the platform specific implementation and the base
+# for file-like behavior. for Python 2.6 and newer, that provide the new I/O
+# library, derive from io.RawIOBase
+try:
+ import io
+except ImportError:
+ # classic version with our own file-like emulation
+ class Serial(PosixSerial, FileLike):
+ pass
+else:
+ # io library present
+ class Serial(PosixSerial, io.RawIOBase):
+ pass
+
+class PosixPollSerial(Serial):
+ """\
+ Poll based read implementation. Not all systems support poll properly.
+ However this one has better handling of errors, such as a device
+ disconnecting while it's in use (e.g. USB-serial unplugged).
+ """
+
+ def read(self, size=1):
+ """\
+ Read size bytes from the serial port. If a timeout is set it may
+ return less characters as requested. With no timeout it will block
+ until the requested number of bytes is read.
+ """
+ if self.fd is None: raise portNotOpenError
+ read = bytearray()
+ poll = select.poll()
+ poll.register(self.fd, select.POLLIN|select.POLLERR|select.POLLHUP|select.POLLNVAL)
+ if size > 0:
+ while len(read) < size:
+ # print "\tread(): size",size, "have", len(read) #debug
+ # wait until device becomes ready to read (or something fails)
+ for fd, event in poll.poll(self._timeout*1000):
+ if event & (select.POLLERR|select.POLLHUP|select.POLLNVAL):
+ raise SerialException('device reports error (poll)')
+ # we don't care if it is select.POLLIN or timeout, that's
+ # handled below
+ buf = os.read(self.fd, size - len(read))
+ read.extend(buf)
+ if ((self._timeout is not None and self._timeout >= 0) or
+ (self._interCharTimeout is not None and self._interCharTimeout > 0)) and not buf:
+ break # early abort on timeout
+ return bytes(read)
+
+
+if __name__ == '__main__':
+ s = Serial(0,
+ baudrate=19200, # baud rate
+ bytesize=EIGHTBITS, # number of data bits
+ parity=PARITY_EVEN, # enable parity checking
+ stopbits=STOPBITS_ONE, # number of stop bits
+ timeout=3, # set a timeout value, None for waiting forever
+ xonxoff=0, # enable software flow control
+ rtscts=0, # enable RTS/CTS flow control
+ )
+ s.setRTS(1)
+ s.setDTR(1)
+ s.flushInput()
+ s.flushOutput()
+ s.write('hello')
+ sys.stdout.write('%r\n' % s.read(5))
+ sys.stdout.write('%s\n' % s.inWaiting())
+ del s
+
diff --git a/scripts/serial/serialutil.py b/scripts/serial/serialutil.py
new file mode 100644
index 0000000..af0d2f6
--- /dev/null
+++ b/scripts/serial/serialutil.py
@@ -0,0 +1,572 @@
+#! python
+# Python Serial Port Extension for Win32, Linux, BSD, Jython
+# see __init__.py
+#
+# (C) 2001-2010 Chris Liechti <cliechti@gmx.net>
+# this is distributed under a free software license, see license.txt
+
+# compatibility for older Python < 2.6
+try:
+ bytes
+ bytearray
+except (NameError, AttributeError):
+ # Python older than 2.6 do not have these types. Like for Python 2.6 they
+ # should behave like str. For Python older than 3.0 we want to work with
+ # strings anyway, only later versions have a true bytes type.
+ bytes = str
+ # bytearray is a mutable type that is easily turned into an instance of
+ # bytes
+ class bytearray(list):
+ # for bytes(bytearray()) usage
+ def __str__(self): return ''.join(self)
+ def __repr__(self): return 'bytearray(%r)' % ''.join(self)
+ # append automatically converts integers to characters
+ def append(self, item):
+ if isinstance(item, str):
+ list.append(self, item)
+ else:
+ list.append(self, chr(item))
+ # +=
+ def __iadd__(self, other):
+ for byte in other:
+ self.append(byte)
+ return self
+
+ def __getslice__(self, i, j):
+ return bytearray(list.__getslice__(self, i, j))
+
+ def __getitem__(self, item):
+ if isinstance(item, slice):
+ return bytearray(list.__getitem__(self, item))
+ else:
+ return ord(list.__getitem__(self, item))
+
+ def __eq__(self, other):
+ if isinstance(other, basestring):
+ other = bytearray(other)
+ return list.__eq__(self, other)
+
+# ``memoryview`` was introduced in Python 2.7 and ``bytes(some_memoryview)``
+# isn't returning the contents (very unfortunate). Therefore we need special
+# cases and test for it. Ensure that there is a ``memoryview`` object for older
+# Python versions. This is easier than making every test dependent on its
+# existence.
+try:
+ memoryview
+except (NameError, AttributeError):
+ # implementation does not matter as we do not realy use it.
+ # it just must not inherit from something else we might care for.
+ class memoryview:
+ pass
+
+
+# all Python versions prior 3.x convert ``str([17])`` to '[17]' instead of '\x11'
+# so a simple ``bytes(sequence)`` doesn't work for all versions
+def to_bytes(seq):
+ """convert a sequence to a bytes type"""
+ if isinstance(seq, bytes):
+ return seq
+ elif isinstance(seq, bytearray):
+ return bytes(seq)
+ elif isinstance(seq, memoryview):
+ return seq.tobytes()
+ else:
+ b = bytearray()
+ for item in seq:
+ b.append(item) # this one handles int and str for our emulation and ints for Python 3.x
+ return bytes(b)
+
+# create control bytes
+XON = to_bytes([17])
+XOFF = to_bytes([19])
+
+CR = to_bytes([13])
+LF = to_bytes([10])
+
+
+PARITY_NONE, PARITY_EVEN, PARITY_ODD, PARITY_MARK, PARITY_SPACE = 'N', 'E', 'O', 'M', 'S'
+STOPBITS_ONE, STOPBITS_ONE_POINT_FIVE, STOPBITS_TWO = (1, 1.5, 2)
+FIVEBITS, SIXBITS, SEVENBITS, EIGHTBITS = (5, 6, 7, 8)
+
+PARITY_NAMES = {
+ PARITY_NONE: 'None',
+ PARITY_EVEN: 'Even',
+ PARITY_ODD: 'Odd',
+ PARITY_MARK: 'Mark',
+ PARITY_SPACE: 'Space',
+}
+
+
+class SerialException(IOError):
+ """Base class for serial port related exceptions."""
+
+
+class SerialTimeoutException(SerialException):
+ """Write timeouts give an exception"""
+
+
+writeTimeoutError = SerialTimeoutException('Write timeout')
+portNotOpenError = SerialException('Attempting to use a port that is not open')
+
+
+class FileLike(object):
+ """\
+ An abstract file like class.
+
+ This class implements readline and readlines based on read and
+ writelines based on write.
+ This class is used to provide the above functions for to Serial
+ port objects.
+
+ Note that when the serial port was opened with _NO_ timeout that
+ readline blocks until it sees a newline (or the specified size is
+ reached) and that readlines would never return and therefore
+ refuses to work (it raises an exception in this case)!
+ """
+
+ def __init__(self):
+ self.closed = True
+
+ def close(self):
+ self.closed = True
+
+ # so that ports are closed when objects are discarded
+ def __del__(self):
+ """Destructor. Calls close()."""
+ # The try/except block is in case this is called at program
+ # exit time, when it's possible that globals have already been
+ # deleted, and then the close() call might fail. Since
+ # there's nothing we can do about such failures and they annoy
+ # the end users, we suppress the traceback.
+ try:
+ self.close()
+ except:
+ pass
+
+ def writelines(self, sequence):
+ for line in sequence:
+ self.write(line)
+
+ def flush(self):
+ """flush of file like objects"""
+ pass
+
+ # iterator for e.g. "for line in Serial(0): ..." usage
+ def next(self):
+ line = self.readline()
+ if not line: raise StopIteration
+ return line
+
+ def __iter__(self):
+ return self
+
+ def readline(self, size=None, eol=LF):
+ """\
+ Read a line which is terminated with end-of-line (eol) character
+ ('\n' by default) or until timeout.
+ """
+ leneol = len(eol)
+ line = bytearray()
+ while True:
+ c = self.read(1)
+ if c:
+ line += c
+ if line[-leneol:] == eol:
+ break
+ if size is not None and len(line) >= size:
+ break
+ else:
+ break
+ return bytes(line)
+
+ def readlines(self, sizehint=None, eol=LF):
+ """\
+ Read a list of lines, until timeout.
+ sizehint is ignored.
+ """
+ if self.timeout is None:
+ raise ValueError("Serial port MUST have enabled timeout for this function!")
+ leneol = len(eol)
+ lines = []
+ while True:
+ line = self.readline(eol=eol)
+ if line:
+ lines.append(line)
+ if line[-leneol:] != eol: # was the line received with a timeout?
+ break
+ else:
+ break
+ return lines
+
+ def xreadlines(self, sizehint=None):
+ """\
+ Read lines, implemented as generator. It will raise StopIteration on
+ timeout (empty read). sizehint is ignored.
+ """
+ while True:
+ line = self.readline()
+ if not line: break
+ yield line
+
+ # other functions of file-likes - not used by pySerial
+
+ #~ readinto(b)
+
+ def seek(self, pos, whence=0):
+ raise IOError("file is not seekable")
+
+ def tell(self):
+ raise IOError("file is not seekable")
+
+ def truncate(self, n=None):
+ raise IOError("file is not seekable")
+
+ def isatty(self):
+ return False
+
+
+class SerialBase(object):
+ """\
+ Serial port base class. Provides __init__ function and properties to
+ get/set port settings.
+ """
+
+ # default values, may be overridden in subclasses that do not support all values
+ BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
+ 9600, 19200, 38400, 57600, 115200, 230400, 460800, 500000,
+ 576000, 921600, 1000000, 1152000, 1500000, 2000000, 2500000,
+ 3000000, 3500000, 4000000)
+ BYTESIZES = (FIVEBITS, SIXBITS, SEVENBITS, EIGHTBITS)
+ PARITIES = (PARITY_NONE, PARITY_EVEN, PARITY_ODD, PARITY_MARK, PARITY_SPACE)
+ STOPBITS = (STOPBITS_ONE, STOPBITS_ONE_POINT_FIVE, STOPBITS_TWO)
+
+ def __init__(self,
+ port = None, # number of device, numbering starts at
+ # zero. if everything fails, the user
+ # can specify a device string, note
+ # that this isn't portable anymore
+ # port will be opened if one is specified
+ baudrate=9600, # baud rate
+ bytesize=EIGHTBITS, # number of data bits
+ parity=PARITY_NONE, # enable parity checking
+ stopbits=STOPBITS_ONE, # number of stop bits
+ timeout=None, # set a timeout value, None to wait forever
+ xonxoff=False, # enable software flow control
+ rtscts=False, # enable RTS/CTS flow control
+ writeTimeout=None, # set a timeout for writes
+ dsrdtr=False, # None: use rtscts setting, dsrdtr override if True or False
+ interCharTimeout=None # Inter-character timeout, None to disable
+ ):
+ """\
+ Initialize comm port object. If a port is given, then the port will be
+ opened immediately. Otherwise a Serial port object in closed state
+ is returned.
+ """
+
+ self._isOpen = False
+ self._port = None # correct value is assigned below through properties
+ self._baudrate = None # correct value is assigned below through properties
+ self._bytesize = None # correct value is assigned below through properties
+ self._parity = None # correct value is assigned below through properties
+ self._stopbits = None # correct value is assigned below through properties
+ self._timeout = None # correct value is assigned below through properties
+ self._writeTimeout = None # correct value is assigned below through properties
+ self._xonxoff = None # correct value is assigned below through properties
+ self._rtscts = None # correct value is assigned below through properties
+ self._dsrdtr = None # correct value is assigned below through properties
+ self._interCharTimeout = None # correct value is assigned below through properties
+
+ # assign values using get/set methods using the properties feature
+ self.port = port
+ self.baudrate = baudrate
+ self.bytesize = bytesize
+ self.parity = parity
+ self.stopbits = stopbits
+ self.timeout = timeout
+ self.writeTimeout = writeTimeout
+ self.xonxoff = xonxoff
+ self.rtscts = rtscts
+ self.dsrdtr = dsrdtr
+ self.interCharTimeout = interCharTimeout
+
+ if port is not None:
+ self.open()
+
+ def isOpen(self):
+ """Check if the port is opened."""
+ return self._isOpen
+
+ # - - - - - - - - - - - - - - - - - - - - - - - -
+
+ # TODO: these are not really needed as the is the BAUDRATES etc. attribute...
+ # maybe i remove them before the final release...
+
+ def getSupportedBaudrates(self):
+ return [(str(b), b) for b in self.BAUDRATES]
+
+ def getSupportedByteSizes(self):
+ return [(str(b), b) for b in self.BYTESIZES]
+
+ def getSupportedStopbits(self):
+ return [(str(b), b) for b in self.STOPBITS]
+
+ def getSupportedParities(self):
+ return [(PARITY_NAMES[b], b) for b in self.PARITIES]
+
+ # - - - - - - - - - - - - - - - - - - - - - - - -
+
+ def setPort(self, port):
+ """\
+ Change the port. The attribute portstr is set to a string that
+ contains the name of the port.
+ """
+
+ was_open = self._isOpen
+ if was_open: self.close()
+ if port is not None:
+ if isinstance(port, basestring):
+ self.portstr = port
+ else:
+ self.portstr = self.makeDeviceName(port)
+ else:
+ self.portstr = None
+ self._port = port
+ self.name = self.portstr
+ if was_open: self.open()
+
+ def getPort(self):
+ """\
+ Get the current port setting. The value that was passed on init or using
+ setPort() is passed back. See also the attribute portstr which contains
+ the name of the port as a string.
+ """
+ return self._port
+
+ port = property(getPort, setPort, doc="Port setting")
+
+
+ def setBaudrate(self, baudrate):
+ """\
+ Change baud rate. It raises a ValueError if the port is open and the
+ baud rate is not possible. If the port is closed, then the value is
+ accepted and the exception is raised when the port is opened.
+ """
+ try:
+ b = int(baudrate)
+ except TypeError:
+ raise ValueError("Not a valid baudrate: %r" % (baudrate,))
+ else:
+ if b <= 0:
+ raise ValueError("Not a valid baudrate: %r" % (baudrate,))
+ self._baudrate = b
+ if self._isOpen: self._reconfigurePort()
+
+ def getBaudrate(self):
+ """Get the current baud rate setting."""
+ return self._baudrate
+
+ baudrate = property(getBaudrate, setBaudrate, doc="Baud rate setting")
+
+
+ def setByteSize(self, bytesize):
+ """Change byte size."""
+ if bytesize not in self.BYTESIZES: raise ValueError("Not a valid byte size: %r" % (bytesize,))
+ self._bytesize = bytesize
+ if self._isOpen: self._reconfigurePort()
+
+ def getByteSize(self):
+ """Get the current byte size setting."""
+ return self._bytesize
+
+ bytesize = property(getByteSize, setByteSize, doc="Byte size setting")
+
+
+ def setParity(self, parity):
+ """Change parity setting."""
+ if parity not in self.PARITIES: raise ValueError("Not a valid parity: %r" % (parity,))
+ self._parity = parity
+ if self._isOpen: self._reconfigurePort()
+
+ def getParity(self):
+ """Get the current parity setting."""
+ return self._parity
+
+ parity = property(getParity, setParity, doc="Parity setting")
+
+
+ def setStopbits(self, stopbits):
+ """Change stop bits size."""
+ if stopbits not in self.STOPBITS: raise ValueError("Not a valid stop bit size: %r" % (stopbits,))
+ self._stopbits = stopbits
+ if self._isOpen: self._reconfigurePort()
+
+ def getStopbits(self):
+ """Get the current stop bits setting."""
+ return self._stopbits
+
+ stopbits = property(getStopbits, setStopbits, doc="Stop bits setting")
+
+
+ def setTimeout(self, timeout):
+ """Change timeout setting."""
+ if timeout is not None:
+ try:
+ timeout + 1 # test if it's a number, will throw a TypeError if not...
+ except TypeError:
+ raise ValueError("Not a valid timeout: %r" % (timeout,))
+ if timeout < 0: raise ValueError("Not a valid timeout: %r" % (timeout,))
+ self._timeout = timeout
+ if self._isOpen: self._reconfigurePort()
+
+ def getTimeout(self):
+ """Get the current timeout setting."""
+ return self._timeout
+
+ timeout = property(getTimeout, setTimeout, doc="Timeout setting for read()")
+
+
+ def setWriteTimeout(self, timeout):
+ """Change timeout setting."""
+ if timeout is not None:
+ if timeout < 0: raise ValueError("Not a valid timeout: %r" % (timeout,))
+ try:
+ timeout + 1 #test if it's a number, will throw a TypeError if not...
+ except TypeError:
+ raise ValueError("Not a valid timeout: %r" % timeout)
+
+ self._writeTimeout = timeout
+ if self._isOpen: self._reconfigurePort()
+
+ def getWriteTimeout(self):
+ """Get the current timeout setting."""
+ return self._writeTimeout
+
+ writeTimeout = property(getWriteTimeout, setWriteTimeout, doc="Timeout setting for write()")
+
+
+ def setXonXoff(self, xonxoff):
+ """Change XON/XOFF setting."""
+ self._xonxoff = xonxoff
+ if self._isOpen: self._reconfigurePort()
+
+ def getXonXoff(self):
+ """Get the current XON/XOFF setting."""
+ return self._xonxoff
+
+ xonxoff = property(getXonXoff, setXonXoff, doc="XON/XOFF setting")
+
+ def setRtsCts(self, rtscts):
+ """Change RTS/CTS flow control setting."""
+ self._rtscts = rtscts
+ if self._isOpen: self._reconfigurePort()
+
+ def getRtsCts(self):
+ """Get the current RTS/CTS flow control setting."""
+ return self._rtscts
+
+ rtscts = property(getRtsCts, setRtsCts, doc="RTS/CTS flow control setting")
+
+ def setDsrDtr(self, dsrdtr=None):
+ """Change DsrDtr flow control setting."""
+ if dsrdtr is None:
+ # if not set, keep backwards compatibility and follow rtscts setting
+ self._dsrdtr = self._rtscts
+ else:
+ # if defined independently, follow its value
+ self._dsrdtr = dsrdtr
+ if self._isOpen: self._reconfigurePort()
+
+ def getDsrDtr(self):
+ """Get the current DSR/DTR flow control setting."""
+ return self._dsrdtr
+
+ dsrdtr = property(getDsrDtr, setDsrDtr, "DSR/DTR flow control setting")
+
+ def setInterCharTimeout(self, interCharTimeout):
+ """Change inter-character timeout setting."""
+ if interCharTimeout is not None:
+ if interCharTimeout < 0: raise ValueError("Not a valid timeout: %r" % interCharTimeout)
+ try:
+ interCharTimeout + 1 # test if it's a number, will throw a TypeError if not...
+ except TypeError:
+ raise ValueError("Not a valid timeout: %r" % interCharTimeout)
+
+ self._interCharTimeout = interCharTimeout
+ if self._isOpen: self._reconfigurePort()
+
+ def getInterCharTimeout(self):
+ """Get the current inter-character timeout setting."""
+ return self._interCharTimeout
+
+ interCharTimeout = property(getInterCharTimeout, setInterCharTimeout, doc="Inter-character timeout setting for read()")
+
+ # - - - - - - - - - - - - - - - - - - - - - - - -
+
+ _SETTINGS = ('baudrate', 'bytesize', 'parity', 'stopbits', 'xonxoff',
+ 'dsrdtr', 'rtscts', 'timeout', 'writeTimeout', 'interCharTimeout')
+
+ def getSettingsDict(self):
+ """\
+ Get current port settings as a dictionary. For use with
+ applySettingsDict.
+ """
+ return dict([(key, getattr(self, '_'+key)) for key in self._SETTINGS])
+
+ def applySettingsDict(self, d):
+ """\
+ apply stored settings from a dictionary returned from
+ getSettingsDict. it's allowed to delete keys from the dictionary. these
+ values will simply left unchanged.
+ """
+ for key in self._SETTINGS:
+ if d[key] != getattr(self, '_'+key): # check against internal "_" value
+ setattr(self, key, d[key]) # set non "_" value to use properties write function
+
+ # - - - - - - - - - - - - - - - - - - - - - - - -
+
+ def __repr__(self):
+ """String representation of the current port settings and its state."""
+ return "%s<id=0x%x, open=%s>(port=%r, baudrate=%r, bytesize=%r, parity=%r, stopbits=%r, timeout=%r, xonxoff=%r, rtscts=%r, dsrdtr=%r)" % (
+ self.__class__.__name__,
+ id(self),
+ self._isOpen,
+ self.portstr,
+ self.baudrate,
+ self.bytesize,
+ self.parity,
+ self.stopbits,
+ self.timeout,
+ self.xonxoff,
+ self.rtscts,
+ self.dsrdtr,
+ )
+
+
+ # - - - - - - - - - - - - - - - - - - - - - - - -
+ # compatibility with io library
+
+ def readable(self): return True
+ def writable(self): return True
+ def seekable(self): return False
+ def readinto(self, b):
+ data = self.read(len(b))
+ n = len(data)
+ try:
+ b[:n] = data
+ except TypeError, err:
+ import array
+ if not isinstance(b, array.array):
+ raise err
+ b[:n] = array.array('b', data)
+ return n
+
+
+if __name__ == '__main__':
+ import sys
+ s = SerialBase()
+ sys.stdout.write('port name: %s\n' % s.portstr)
+ sys.stdout.write('baud rates: %s\n' % s.getSupportedBaudrates())
+ sys.stdout.write('byte sizes: %s\n' % s.getSupportedByteSizes())
+ sys.stdout.write('parities: %s\n' % s.getSupportedParities())
+ sys.stdout.write('stop bits: %s\n' % s.getSupportedStopbits())
+ sys.stdout.write('%s\n' % s)
diff --git a/scripts/serial/tools/__init__.py b/scripts/serial/tools/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/scripts/serial/tools/list_ports.py b/scripts/serial/tools/list_ports.py
new file mode 100644
index 0000000..d373a55
--- /dev/null
+++ b/scripts/serial/tools/list_ports.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python
+
+# portable serial port access with python
+# this is a wrapper module for different platform implementations of the
+# port enumeration feature
+#
+# (C) 2011-2013 Chris Liechti <cliechti@gmx.net>
+# this is distributed under a free software license, see license.txt
+
+"""\
+This module will provide a function called comports that returns an
+iterable (generator or list) that will enumerate available com ports. Note that
+on some systems non-existent ports may be listed.
+
+Additionally a grep function is supplied that can be used to search for ports
+based on their descriptions or hardware ID.
+"""
+
+import sys, os, re
+
+# chose an implementation, depending on os
+#~ if sys.platform == 'cli':
+#~ else:
+import os
+# chose an implementation, depending on os
+if os.name == 'nt': #sys.platform == 'win32':
+ from serial.tools.list_ports_windows import *
+elif os.name == 'posix':
+ from serial.tools.list_ports_posix import *
+#~ elif os.name == 'java':
+else:
+ raise ImportError("Sorry: no implementation for your platform ('%s') available" % (os.name,))
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+def grep(regexp):
+ """\
+ Search for ports using a regular expression. Port name, description and
+ hardware ID are searched. The function returns an iterable that returns the
+ same tuples as comport() would do.
+ """
+ r = re.compile(regexp, re.I)
+ for port, desc, hwid in comports():
+ if r.search(port) or r.search(desc) or r.search(hwid):
+ yield port, desc, hwid
+
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+def main():
+ import optparse
+
+ parser = optparse.OptionParser(
+ usage = "%prog [options] [<regexp>]",
+ description = "Miniterm - A simple terminal program for the serial port."
+ )
+
+ parser.add_option("--debug",
+ help="print debug messages and tracebacks (development mode)",
+ dest="debug",
+ default=False,
+ action='store_true')
+
+ parser.add_option("-v", "--verbose",
+ help="show more messages (can be given multiple times)",
+ dest="verbose",
+ default=1,
+ action='count')
+
+ parser.add_option("-q", "--quiet",
+ help="suppress all messages",
+ dest="verbose",
+ action='store_const',
+ const=0)
+
+ (options, args) = parser.parse_args()
+
+
+ hits = 0
+ # get iteraror w/ or w/o filter
+ if args:
+ if len(args) > 1:
+ parser.error('more than one regexp not supported')
+ print "Filtered list with regexp: %r" % (args[0],)
+ iterator = sorted(grep(args[0]))
+ else:
+ iterator = sorted(comports())
+ # list them
+ for port, desc, hwid in iterator:
+ print("%-20s" % (port,))
+ if options.verbose > 1:
+ print(" desc: %s" % (desc,))
+ print(" hwid: %s" % (hwid,))
+ hits += 1
+ if options.verbose:
+ if hits:
+ print("%d ports found" % (hits,))
+ else:
+ print("no ports found")
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# test
+if __name__ == '__main__':
+ main()
diff --git a/scripts/serial/tools/list_ports_linux.py b/scripts/serial/tools/list_ports_linux.py
new file mode 100644
index 0000000..955761e
--- /dev/null
+++ b/scripts/serial/tools/list_ports_linux.py
@@ -0,0 +1,152 @@
+#!/usr/bin/env python
+
+# portable serial port access with python
+#
+# This is a module that gathers a list of serial ports including details on
+# GNU/Linux systems
+#
+# (C) 2011-2013 Chris Liechti <cliechti@gmx.net>
+# this is distributed under a free software license, see license.txt
+
+import glob
+import sys
+import os
+import re
+
+try:
+ import subprocess
+except ImportError:
+ def popen(argv):
+ try:
+ si, so = os.popen4(' '.join(argv))
+ return so.read().strip()
+ except:
+ raise IOError('lsusb failed')
+else:
+ def popen(argv):
+ try:
+ return subprocess.check_output(argv, stderr=subprocess.STDOUT).strip()
+ except:
+ raise IOError('lsusb failed')
+
+
+# The comports function is expected to return an iterable that yields tuples of
+# 3 strings: port name, human readable description and a hardware ID.
+#
+# as currently no method is known to get the second two strings easily, they
+# are currently just identical to the port name.
+
+# try to detect the OS so that a device can be selected...
+plat = sys.platform.lower()
+
+def read_line(filename):
+ """\
+ Helper function to read a single line from a file.
+ Returns None on errors..
+ """
+ try:
+ f = open(filename)
+ line = f.readline().strip()
+ f.close()
+ return line
+ except IOError:
+ return None
+
+def re_group(regexp, text):
+ """search for regexp in text, return 1st group on match"""
+ if sys.version < '3':
+ m = re.search(regexp, text)
+ else:
+ # text is bytes-like
+ m = re.search(regexp, text.decode('ascii', 'replace'))
+ if m: return m.group(1)
+
+
+# try to extract descriptions from sysfs. this was done by experimenting,
+# no guarantee that it works for all devices or in the future...
+
+def usb_sysfs_hw_string(sysfs_path):
+ """given a path to a usb device in sysfs, return a string describing it"""
+ bus, dev = os.path.basename(os.path.realpath(sysfs_path)).split('-')
+ snr = read_line(sysfs_path+'/serial')
+ if snr:
+ snr_txt = ' SNR=%s' % (snr,)
+ else:
+ snr_txt = ''
+ return 'USB VID:PID=%s:%s%s' % (
+ read_line(sysfs_path+'/idVendor'),
+ read_line(sysfs_path+'/idProduct'),
+ snr_txt
+ )
+
+def usb_lsusb_string(sysfs_path):
+ base = os.path.basename(os.path.realpath(sysfs_path))
+ bus = base.split('-')[0]
+ try:
+ dev = int(read_line(os.path.join(sysfs_path, 'devnum')))
+ desc = popen(['lsusb', '-v', '-s', '%s:%s' % (bus, dev)])
+ # descriptions from device
+ iManufacturer = re_group('iManufacturer\s+\w+ (.+)', desc)
+ iProduct = re_group('iProduct\s+\w+ (.+)', desc)
+ iSerial = re_group('iSerial\s+\w+ (.+)', desc) or ''
+ # descriptions from kernel
+ idVendor = re_group('idVendor\s+0x\w+ (.+)', desc)
+ idProduct = re_group('idProduct\s+0x\w+ (.+)', desc)
+ # create descriptions. prefer text from device, fall back to the others
+ return '%s %s %s' % (iManufacturer or idVendor, iProduct or idProduct, iSerial)
+ except IOError:
+ return base
+
+def describe(device):
+ """\
+ Get a human readable description.
+ For USB-Serial devices try to run lsusb to get a human readable description.
+ For USB-CDC devices read the description from sysfs.
+ """
+ base = os.path.basename(device)
+ # USB-Serial devices
+ sys_dev_path = '/sys/class/tty/%s/device/driver/%s' % (base, base)
+ if os.path.exists(sys_dev_path):
+ sys_usb = os.path.dirname(os.path.dirname(os.path.realpath(sys_dev_path)))
+ return usb_lsusb_string(sys_usb)
+ # USB-CDC devices
+ sys_dev_path = '/sys/class/tty/%s/device/interface' % (base,)
+ if os.path.exists(sys_dev_path):
+ return read_line(sys_dev_path)
+ # USB Product Information
+ sys_dev_path = '/sys/class/tty/%s/device' % (base,)
+ if os.path.exists(sys_dev_path):
+ product_name_file = os.path.dirname(os.path.realpath(sys_dev_path)) + "/product"
+ if os.path.exists(product_name_file):
+ return read_line(product_name_file)
+ return base
+
+def hwinfo(device):
+ """Try to get a HW identification using sysfs"""
+ base = os.path.basename(device)
+ if os.path.exists('/sys/class/tty/%s/device' % (base,)):
+ # PCI based devices
+ sys_id_path = '/sys/class/tty/%s/device/id' % (base,)
+ if os.path.exists(sys_id_path):
+ return read_line(sys_id_path)
+ # USB-Serial devices
+ sys_dev_path = '/sys/class/tty/%s/device/driver/%s' % (base, base)
+ if os.path.exists(sys_dev_path):
+ sys_usb = os.path.dirname(os.path.dirname(os.path.realpath(sys_dev_path)))
+ return usb_sysfs_hw_string(sys_usb)
+ # USB-CDC devices
+ if base.startswith('ttyACM'):
+ sys_dev_path = '/sys/class/tty/%s/device' % (base,)
+ if os.path.exists(sys_dev_path):
+ return usb_sysfs_hw_string(sys_dev_path + '/..')
+ return 'n/a' # XXX directly remove these from the list?
+
+def comports():
+ devices = glob.glob('/dev/ttyS*') + glob.glob('/dev/ttyUSB*') + glob.glob('/dev/ttyACM*')
+ return [(d, describe(d), hwinfo(d)) for d in devices]
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# test
+if __name__ == '__main__':
+ for port, desc, hwid in sorted(comports()):
+ print "%s: %s [%s]" % (port, desc, hwid)
diff --git a/scripts/serial/urlhandler/__init__.py b/scripts/serial/urlhandler/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/scripts/serial/urlhandler/protocol_hwgrep.py b/scripts/serial/urlhandler/protocol_hwgrep.py
new file mode 100644
index 0000000..62cda43
--- /dev/null
+++ b/scripts/serial/urlhandler/protocol_hwgrep.py
@@ -0,0 +1,45 @@
+#! python
+#
+# Python Serial Port Extension for Win32, Linux, BSD, Jython
+# see __init__.py
+#
+# This module implements a special URL handler that uses the port listing to
+# find ports by searching the string descriptions.
+#
+# (C) 2011 Chris Liechti <cliechti@gmx.net>
+# this is distributed under a free software license, see license.txt
+#
+# URL format: hwgrep://regexp
+
+import serial
+import serial.tools.list_ports
+
+class Serial(serial.Serial):
+ """Just inherit the native Serial port implementation and patch the open function."""
+
+ def setPort(self, value):
+ """translate port name before storing it"""
+ if isinstance(value, basestring) and value.startswith('hwgrep://'):
+ serial.Serial.setPort(self, self.fromURL(value))
+ else:
+ serial.Serial.setPort(self, value)
+
+ def fromURL(self, url):
+ """extract host and port from an URL string"""
+ if url.lower().startswith("hwgrep://"): url = url[9:]
+ # use a for loop to get the 1st element from the generator
+ for port, desc, hwid in serial.tools.list_ports.grep(url):
+ return port
+ else:
+ raise serial.SerialException('no ports found matching regexp %r' % (url,))
+
+ # override property
+ port = property(serial.Serial.getPort, setPort, doc="Port setting")
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+if __name__ == '__main__':
+ #~ s = Serial('hwgrep://ttyS0')
+ s = Serial(None)
+ s.port = 'hwgrep://ttyS0'
+ print s
+
diff --git a/scripts/serial/urlhandler/protocol_loop.py b/scripts/serial/urlhandler/protocol_loop.py
new file mode 100644
index 0000000..a414839
--- /dev/null
+++ b/scripts/serial/urlhandler/protocol_loop.py
@@ -0,0 +1,279 @@
+#! python
+#
+# Python Serial Port Extension for Win32, Linux, BSD, Jython
+# see __init__.py
+#
+# This module implements a loop back connection receiving itself what it sent.
+#
+# The purpose of this module is.. well... You can run the unit tests with it.
+# and it was so easy to implement ;-)
+#
+# (C) 2001-2011 Chris Liechti <cliechti@gmx.net>
+# this is distributed under a free software license, see license.txt
+#
+# URL format: loop://[option[/option...]]
+# options:
+# - "debug" print diagnostic messages
+
+from serial.serialutil import *
+import threading
+import time
+import logging
+
+# map log level names to constants. used in fromURL()
+LOGGER_LEVELS = {
+ 'debug': logging.DEBUG,
+ 'info': logging.INFO,
+ 'warning': logging.WARNING,
+ 'error': logging.ERROR,
+ }
+
+
+class LoopbackSerial(SerialBase):
+ """Serial port implementation that simulates a loop back connection in plain software."""
+
+ BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
+ 9600, 19200, 38400, 57600, 115200)
+
+ def open(self):
+ """\
+ Open port with current settings. This may throw a SerialException
+ if the port cannot be opened.
+ """
+ if self._isOpen:
+ raise SerialException("Port is already open.")
+ self.logger = None
+ self.buffer_lock = threading.Lock()
+ self.loop_buffer = bytearray()
+ self.cts = False
+ self.dsr = False
+
+ if self._port is None:
+ raise SerialException("Port must be configured before it can be used.")
+ # not that there is anything to open, but the function applies the
+ # options found in the URL
+ self.fromURL(self.port)
+
+ # not that there anything to configure...
+ self._reconfigurePort()
+ # all things set up get, now a clean start
+ self._isOpen = True
+ if not self._rtscts:
+ self.setRTS(True)
+ self.setDTR(True)
+ self.flushInput()
+ self.flushOutput()
+
+ def _reconfigurePort(self):
+ """\
+ Set communication parameters on opened port. For the loop://
+ protocol all settings are ignored!
+ """
+ # not that's it of any real use, but it helps in the unit tests
+ if not isinstance(self._baudrate, (int, long)) or not 0 < self._baudrate < 2**32:
+ raise ValueError("invalid baudrate: %r" % (self._baudrate))
+ if self.logger:
+ self.logger.info('_reconfigurePort()')
+
+ def close(self):
+ """Close port"""
+ if self._isOpen:
+ self._isOpen = False
+ # in case of quick reconnects, give the server some time
+ time.sleep(0.3)
+
+ def makeDeviceName(self, port):
+ raise SerialException("there is no sensible way to turn numbers into URLs")
+
+ def fromURL(self, url):
+ """extract host and port from an URL string"""
+ if url.lower().startswith("loop://"): url = url[7:]
+ try:
+ # process options now, directly altering self
+ for option in url.split('/'):
+ if '=' in option:
+ option, value = option.split('=', 1)
+ else:
+ value = None
+ if not option:
+ pass
+ elif option == 'logging':
+ logging.basicConfig() # XXX is that good to call it here?
+ self.logger = logging.getLogger('pySerial.loop')
+ self.logger.setLevel(LOGGER_LEVELS[value])
+ self.logger.debug('enabled logging')
+ else:
+ raise ValueError('unknown option: %r' % (option,))
+ except ValueError, e:
+ raise SerialException('expected a string in the form "[loop://][option[/option...]]": %s' % e)
+
+ # - - - - - - - - - - - - - - - - - - - - - - - -
+
+ def inWaiting(self):
+ """Return the number of characters currently in the input buffer."""
+ if not self._isOpen: raise portNotOpenError
+ if self.logger:
+ # attention the logged value can differ from return value in
+ # threaded environments...
+ self.logger.debug('inWaiting() -> %d' % (len(self.loop_buffer),))
+ return len(self.loop_buffer)
+
+ def read(self, size=1):
+ """\
+ Read size bytes from the serial port. If a timeout is set it may
+ return less characters as requested. With no timeout it will block
+ until the requested number of bytes is read.
+ """
+ if not self._isOpen: raise portNotOpenError
+ if self._timeout is not None:
+ timeout = time.time() + self._timeout
+ else:
+ timeout = None
+ data = bytearray()
+ while size > 0:
+ self.buffer_lock.acquire()
+ try:
+ block = to_bytes(self.loop_buffer[:size])
+ del self.loop_buffer[:size]
+ finally:
+ self.buffer_lock.release()
+ data += block
+ size -= len(block)
+ # check for timeout now, after data has been read.
+ # useful for timeout = 0 (non blocking) read
+ if timeout and time.time() > timeout:
+ break
+ return bytes(data)
+
+ def write(self, data):
+ """\
+ Output the given string over the serial port. Can block if the
+ connection is blocked. May raise SerialException if the connection is
+ closed.
+ """
+ if not self._isOpen: raise portNotOpenError
+ # ensure we're working with bytes
+ data = to_bytes(data)
+ # calculate aprox time that would be used to send the data
+ time_used_to_send = 10.0*len(data) / self._baudrate
+ # when a write timeout is configured check if we would be successful
+ # (not sending anything, not even the part that would have time)
+ if self._writeTimeout is not None and time_used_to_send > self._writeTimeout:
+ time.sleep(self._writeTimeout) # must wait so that unit test succeeds
+ raise writeTimeoutError
+ self.buffer_lock.acquire()
+ try:
+ self.loop_buffer += data
+ finally:
+ self.buffer_lock.release()
+ return len(data)
+
+ def flushInput(self):
+ """Clear input buffer, discarding all that is in the buffer."""
+ if not self._isOpen: raise portNotOpenError
+ if self.logger:
+ self.logger.info('flushInput()')
+ self.buffer_lock.acquire()
+ try:
+ del self.loop_buffer[:]
+ finally:
+ self.buffer_lock.release()
+
+ def flushOutput(self):
+ """\
+ Clear output buffer, aborting the current output and
+ discarding all that is in the buffer.
+ """
+ if not self._isOpen: raise portNotOpenError
+ if self.logger:
+ self.logger.info('flushOutput()')
+
+ def sendBreak(self, duration=0.25):
+ """\
+ Send break condition. Timed, returns to idle state after given
+ duration.
+ """
+ if not self._isOpen: raise portNotOpenError
+
+ def setBreak(self, level=True):
+ """\
+ Set break: Controls TXD. When active, to transmitting is
+ possible.
+ """
+ if not self._isOpen: raise portNotOpenError
+ if self.logger:
+ self.logger.info('setBreak(%r)' % (level,))
+
+ def setRTS(self, level=True):
+ """Set terminal status line: Request To Send"""
+ if not self._isOpen: raise portNotOpenError
+ if self.logger:
+ self.logger.info('setRTS(%r) -> state of CTS' % (level,))
+ self.cts = level
+
+ def setDTR(self, level=True):
+ """Set terminal status line: Data Terminal Ready"""
+ if not self._isOpen: raise portNotOpenError
+ if self.logger:
+ self.logger.info('setDTR(%r) -> state of DSR' % (level,))
+ self.dsr = level
+
+ def getCTS(self):
+ """Read terminal status line: Clear To Send"""
+ if not self._isOpen: raise portNotOpenError
+ if self.logger:
+ self.logger.info('getCTS() -> state of RTS (%r)' % (self.cts,))
+ return self.cts
+
+ def getDSR(self):
+ """Read terminal status line: Data Set Ready"""
+ if not self._isOpen: raise portNotOpenError
+ if self.logger:
+ self.logger.info('getDSR() -> state of DTR (%r)' % (self.dsr,))
+ return self.dsr
+
+ def getRI(self):
+ """Read terminal status line: Ring Indicator"""
+ if not self._isOpen: raise portNotOpenError
+ if self.logger:
+ self.logger.info('returning dummy for getRI()')
+ return False
+
+ def getCD(self):
+ """Read terminal status line: Carrier Detect"""
+ if not self._isOpen: raise portNotOpenError
+ if self.logger:
+ self.logger.info('returning dummy for getCD()')
+ return True
+
+ # - - - platform specific - - -
+ # None so far
+
+
+# assemble Serial class with the platform specific implementation and the base
+# for file-like behavior. for Python 2.6 and newer, that provide the new I/O
+# library, derive from io.RawIOBase
+try:
+ import io
+except ImportError:
+ # classic version with our own file-like emulation
+ class Serial(LoopbackSerial, FileLike):
+ pass
+else:
+ # io library present
+ class Serial(LoopbackSerial, io.RawIOBase):
+ pass
+
+
+# simple client test
+if __name__ == '__main__':
+ import sys
+ s = Serial('loop://')
+ sys.stdout.write('%s\n' % s)
+
+ sys.stdout.write("write...\n")
+ s.write("hello\n")
+ s.flush()
+ sys.stdout.write("read: %s\n" % s.read(5))
+
+ s.close()
diff --git a/scripts/serial/urlhandler/protocol_rfc2217.py b/scripts/serial/urlhandler/protocol_rfc2217.py
new file mode 100644
index 0000000..981ba45
--- /dev/null
+++ b/scripts/serial/urlhandler/protocol_rfc2217.py
@@ -0,0 +1,11 @@
+#! python
+#
+# Python Serial Port Extension for Win32, Linux, BSD, Jython
+# see ../__init__.py
+#
+# This is a thin wrapper to load the rfc2271 implementation.
+#
+# (C) 2011 Chris Liechti <cliechti@gmx.net>
+# this is distributed under a free software license, see license.txt
+
+from serial.rfc2217 import Serial
diff --git a/scripts/serial/urlhandler/protocol_socket.py b/scripts/serial/urlhandler/protocol_socket.py
new file mode 100644
index 0000000..dc59923
--- /dev/null
+++ b/scripts/serial/urlhandler/protocol_socket.py
@@ -0,0 +1,291 @@
+#! python
+#
+# Python Serial Port Extension for Win32, Linux, BSD, Jython
+# see __init__.py
+#
+# This module implements a simple socket based client.
+# It does not support changing any port parameters and will silently ignore any
+# requests to do so.
+#
+# The purpose of this module is that applications using pySerial can connect to
+# TCP/IP to serial port converters that do not support RFC 2217.
+#
+# (C) 2001-2011 Chris Liechti <cliechti@gmx.net>
+# this is distributed under a free software license, see license.txt
+#
+# URL format: socket://<host>:<port>[/option[/option...]]
+# options:
+# - "debug" print diagnostic messages
+
+from serial.serialutil import *
+import time
+import socket
+import select
+import logging
+
+# map log level names to constants. used in fromURL()
+LOGGER_LEVELS = {
+ 'debug': logging.DEBUG,
+ 'info': logging.INFO,
+ 'warning': logging.WARNING,
+ 'error': logging.ERROR,
+ }
+
+POLL_TIMEOUT = 2
+
+class SocketSerial(SerialBase):
+ """Serial port implementation for plain sockets."""
+
+ BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
+ 9600, 19200, 38400, 57600, 115200)
+
+ def open(self):
+ """\
+ Open port with current settings. This may throw a SerialException
+ if the port cannot be opened.
+ """
+ self.logger = None
+ if self._port is None:
+ raise SerialException("Port must be configured before it can be used.")
+ if self._isOpen:
+ raise SerialException("Port is already open.")
+ try:
+ # XXX in future replace with create_connection (py >=2.6)
+ self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self._socket.connect(self.fromURL(self.portstr))
+ except Exception, msg:
+ self._socket = None
+ raise SerialException("Could not open port %s: %s" % (self.portstr, msg))
+
+ self._socket.settimeout(POLL_TIMEOUT) # used for write timeout support :/
+
+ # not that there anything to configure...
+ self._reconfigurePort()
+ # all things set up get, now a clean start
+ self._isOpen = True
+ if not self._rtscts:
+ self.setRTS(True)
+ self.setDTR(True)
+ self.flushInput()
+ self.flushOutput()
+
+ def _reconfigurePort(self):
+ """\
+ Set communication parameters on opened port. For the socket://
+ protocol all settings are ignored!
+ """
+ if self._socket is None:
+ raise SerialException("Can only operate on open ports")
+ if self.logger:
+ self.logger.info('ignored port configuration change')
+
+ def close(self):
+ """Close port"""
+ if self._isOpen:
+ if self._socket:
+ try:
+ self._socket.shutdown(socket.SHUT_RDWR)
+ self._socket.close()
+ except:
+ # ignore errors.
+ pass
+ self._socket = None
+ self._isOpen = False
+ # in case of quick reconnects, give the server some time
+ time.sleep(0.3)
+
+ def makeDeviceName(self, port):
+ raise SerialException("there is no sensible way to turn numbers into URLs")
+
+ def fromURL(self, url):
+ """extract host and port from an URL string"""
+ if url.lower().startswith("socket://"): url = url[9:]
+ try:
+ # is there a "path" (our options)?
+ if '/' in url:
+ # cut away options
+ url, options = url.split('/', 1)
+ # process options now, directly altering self
+ for option in options.split('/'):
+ if '=' in option:
+ option, value = option.split('=', 1)
+ else:
+ value = None
+ if option == 'logging':
+ logging.basicConfig() # XXX is that good to call it here?
+ self.logger = logging.getLogger('pySerial.socket')
+ self.logger.setLevel(LOGGER_LEVELS[value])
+ self.logger.debug('enabled logging')
+ else:
+ raise ValueError('unknown option: %r' % (option,))
+ # get host and port
+ host, port = url.split(':', 1) # may raise ValueError because of unpacking
+ port = int(port) # and this if it's not a number
+ if not 0 <= port < 65536: raise ValueError("port not in range 0...65535")
+ except ValueError, e:
+ raise SerialException('expected a string in the form "[rfc2217://]<host>:<port>[/option[/option...]]": %s' % e)
+ return (host, port)
+
+ # - - - - - - - - - - - - - - - - - - - - - - - -
+
+ def inWaiting(self):
+ """Return the number of characters currently in the input buffer."""
+ if not self._isOpen: raise portNotOpenError
+ # Poll the socket to see if it is ready for reading.
+ # If ready, at least one byte will be to read.
+ lr, lw, lx = select.select([self._socket], [], [], 0)
+ return len(lr)
+
+ def read(self, size=1):
+ """\
+ Read size bytes from the serial port. If a timeout is set it may
+ return less characters as requested. With no timeout it will block
+ until the requested number of bytes is read.
+ """
+ if not self._isOpen: raise portNotOpenError
+ data = bytearray()
+ if self._timeout is not None:
+ timeout = time.time() + self._timeout
+ else:
+ timeout = None
+ while len(data) < size and (timeout is None or time.time() < timeout):
+ try:
+ # an implementation with internal buffer would be better
+ # performing...
+ t = time.time()
+ block = self._socket.recv(size - len(data))
+ duration = time.time() - t
+ if block:
+ data.extend(block)
+ else:
+ # no data -> EOF (connection probably closed)
+ break
+ except socket.timeout:
+ # just need to get out of recv from time to time to check if
+ # still alive
+ continue
+ except socket.error, e:
+ # connection fails -> terminate loop
+ raise SerialException('connection failed (%s)' % e)
+ return bytes(data)
+
+ def write(self, data):
+ """\
+ Output the given string over the serial port. Can block if the
+ connection is blocked. May raise SerialException if the connection is
+ closed.
+ """
+ if not self._isOpen: raise portNotOpenError
+ try:
+ self._socket.sendall(to_bytes(data))
+ except socket.error, e:
+ # XXX what exception if socket connection fails
+ raise SerialException("socket connection failed: %s" % e)
+ return len(data)
+
+ def flushInput(self):
+ """Clear input buffer, discarding all that is in the buffer."""
+ if not self._isOpen: raise portNotOpenError
+ if self.logger:
+ self.logger.info('ignored flushInput')
+
+ def flushOutput(self):
+ """\
+ Clear output buffer, aborting the current output and
+ discarding all that is in the buffer.
+ """
+ if not self._isOpen: raise portNotOpenError
+ if self.logger:
+ self.logger.info('ignored flushOutput')
+
+ def sendBreak(self, duration=0.25):
+ """\
+ Send break condition. Timed, returns to idle state after given
+ duration.
+ """
+ if not self._isOpen: raise portNotOpenError
+ if self.logger:
+ self.logger.info('ignored sendBreak(%r)' % (duration,))
+
+ def setBreak(self, level=True):
+ """Set break: Controls TXD. When active, to transmitting is
+ possible."""
+ if not self._isOpen: raise portNotOpenError
+ if self.logger:
+ self.logger.info('ignored setBreak(%r)' % (level,))
+
+ def setRTS(self, level=True):
+ """Set terminal status line: Request To Send"""
+ if not self._isOpen: raise portNotOpenError
+ if self.logger:
+ self.logger.info('ignored setRTS(%r)' % (level,))
+
+ def setDTR(self, level=True):
+ """Set terminal status line: Data Terminal Ready"""
+ if not self._isOpen: raise portNotOpenError
+ if self.logger:
+ self.logger.info('ignored setDTR(%r)' % (level,))
+
+ def getCTS(self):
+ """Read terminal status line: Clear To Send"""
+ if not self._isOpen: raise portNotOpenError
+ if self.logger:
+ self.logger.info('returning dummy for getCTS()')
+ return True
+
+ def getDSR(self):
+ """Read terminal status line: Data Set Ready"""
+ if not self._isOpen: raise portNotOpenError
+ if self.logger:
+ self.logger.info('returning dummy for getDSR()')
+ return True
+
+ def getRI(self):
+ """Read terminal status line: Ring Indicator"""
+ if not self._isOpen: raise portNotOpenError
+ if self.logger:
+ self.logger.info('returning dummy for getRI()')
+ return False
+
+ def getCD(self):
+ """Read terminal status line: Carrier Detect"""
+ if not self._isOpen: raise portNotOpenError
+ if self.logger:
+ self.logger.info('returning dummy for getCD()')
+ return True
+
+ # - - - platform specific - - -
+
+ # works on Linux and probably all the other POSIX systems
+ def fileno(self):
+ """Get the file handle of the underlying socket for use with select"""
+ return self._socket.fileno()
+
+
+# assemble Serial class with the platform specific implementation and the base
+# for file-like behavior. for Python 2.6 and newer, that provide the new I/O
+# library, derive from io.RawIOBase
+try:
+ import io
+except ImportError:
+ # classic version with our own file-like emulation
+ class Serial(SocketSerial, FileLike):
+ pass
+else:
+ # io library present
+ class Serial(SocketSerial, io.RawIOBase):
+ pass
+
+
+# simple client test
+if __name__ == '__main__':
+ import sys
+ s = Serial('socket://localhost:7000')
+ sys.stdout.write('%s\n' % s)
+
+ sys.stdout.write("write...\n")
+ s.write("hello\n")
+ s.flush()
+ sys.stdout.write("read: %s\n" % s.read(5))
+
+ s.close()
--
2.1.4
_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox
^ permalink raw reply [flat|nested] 12+ messages in thread