Overview of the Serial Communication Protocol
Serial on a Server
Any time you are connected to a server over SSH, you are using a serial protocol. Today, it is carried by TCP/IP, along with all of your other network traffic, rather than over a dedicated line, but the principle is the same. When you open a terminal window in your favorite Linux distro, you're actually emulating what used to be a hard-wired serial connection.
You can still set up a hardware serial terminal as a console to your Linux machine. The first step is to get it wired to the computer. You probably have a 9-pin serial port, so you'll need a 9-pin serial cable. Most hardware terminals use a 25-pin connector, so you also need a 9-pin to 25-pin adapter, which you can easily find on Amazon or at your local electronics shop.
Once everything is connected, look at the manual for your terminal to find out how to set up the baud rate and other connection parameters, which should be 115,200 baud, 8 data bits, no parity bit, and 1 stop bit.
Once you are wired up, you need software to present a shell to the terminal over the serial line. A program called agetty
should already be available in your distribution or available from the package manager. Open a terminal window on your desktop and type agetty. If it says not enough arguments you're good to go. The man page shows the command format:
agetty [options] port [baud_rate...]
See the "Finding Your Serial Port" box to determine your port name; then, enter something like:
Finding Your Serial Port
Linux reports all of its hardware, including serial ports, as files in /dev
. If your computer has a port on the back of the CPU, then it is probably found at /dev/ttyS0
. If you're trying to talk to an Arduino, it is probably /dev/ttyAMA0
, whereas if you're using a USB dongle, it is probably something like /dev/ttyUSB0
. You can find your port quickly and easily as follows:
- Make sure your USB serial port or Arduino is unplugged
- In a terminal, enter
ls -1 /dev/tty*>noUSB.txt
- Connect your USB serial port and give it a second or so to register
- In the terminal, enter
ls -1 /dev/tty*>USB.txt
- Finally, enter
diff noUSB.txt USB.txt
You'll see something like:
98a99 > /dev/ttyUSB0
In steps 2 and 4 above, you're using ls
to get a list of tty
devices found in /dev
. The -1
requests output of one entry per line instead of output in columns. The >noUSB.txt
redirects output to a text file. After connecting a USB device, step 4 makes a new list that will include the device that's just been connected, and step 5, the diff
command, compares the two output files.
The first line of output reports that after line 98 in the original file (noUSB.txt
) the second file (USB.txt
) has an additional (a
) line 99. The next line of output shows that line after the chevron. In this case, that's the name of the USB serial port that appeared when the device was connected.
agetty ttyUSB0 115200
If all goes well, a login prompt should appear on your hard-wired terminal (Figure 4). Once you log in, the system's default shell will appear. You can also configure agetty
to run a custom handler when something connects (e.g., any kind of service you've written), pass the connection off to another program to start a dial-up Internet connection, or anything along those lines.
Curses Library
Whether you're using a hard-wired terminal or just a virtual terminal in a window on your computer, this example will work just fine. Because so many different terminals are available with different sizes, capabilities, and options, the curses library [5] was developed to handle all of the details in the background, so the developer can just write code and not worry about the capabilities of the underlying hardware. If a particular feature isn't available on a terminal, curses will downgrade the features until it can be displayed. If necessary, the output will just be unstyled black and white text.
The Big Print Clock
A popular thing to do in a serial terminal is display big block text several lines tall. The bigPrint.py
Python script shows a clock in block text (Figure 5) and is a great example of an application of the curses
library.
In the Python program in Listing 4, import
brings in the curses, time, os, string, and datetime libraries. Note that line 5 is a little different: Rather than importing the entire library, it only imports the datetime
module. By default, Python imports entire libraries, so an import
in this format allows you to bring in only the parts you need.
Listing 4
clock.py
001 import curses 002 import time 003 import os 004 import string 005 from datetime import datetime 006 007 class console: 008 def __init__ ( self ): 009 self.screen = curses.initscr() 010 curses.noecho() 011 curses.cbreak() 012 self.screen.nodelay ( True ) 013 self.screen.keypad ( True ) 014 015 self.digits = dict() 016 self.digits [ '0' ] = """ 017 ### 018 # # 019 # # 020 # # 021 ### 022 """ 023 self.digits [ '1' ] = """ 024 # 025 ## 026 # 027 # 028 ##### 029 """ 030 self.digits [ '2' ] = """ 031 ### 032 # # 033 # 034 # 035 ##### 036 """ 037 self.digits [ '3' ] = """ 038 ### 039 # # 040 # 041 # # 042 ### 043 """ 044 self.digits [ '4' ] = """ 045 # # 046 # # 047 ##### 048 # 049 # 050 """ 051 self.digits [ '5' ] = """ 052 ##### 053 # 054 ### 055 # 056 #### 057 """ 058 self.digits [ '6' ] = """ 059 ### 060 # 061 #### 062 # # 063 ### 064 """ 065 self.digits [ '7' ] = """ 066 ##### 067 # 068 # 069 # 070 # 071 """ 072 self.digits [ '8' ] = """ 073 ### 074 # # 075 ### 076 # # 077 ### 078 """ 079 self.digits [ '9' ] = """ 080 ### 081 # # 082 #### 083 # 084 ### 085 """ 086 self.digits [ ':' ] = """ 087 088 # 089 090 # 091 092 """ 093 094 def bigPrint ( self , text , position ): 095 x = position [ 1 ] 096 y = position [ 0 ] 097 originalY = y 098 099 for char in text: 100 if char in self.digits: 101 firstLine = True 102 for line in self.digits [ char ].split ( "\n" ): 103 if firstLine == True: 104 firstLine = False 105 continue 106 outLine = "" 107 for char in line: 108 if char == "#": outLine += chr ( 97 ) 109 else: outLine += char 110 self.screen.addstr ( y , x , "{0:5s}".format ( outLine ) , curses.A_ALTCHARSET ) 111 y += 1 112 if y > ( originalY + 4 ): break 113 y = originalY 114 x += 7 115 116 def loop ( self ): 117 looping = True 118 startTime = time.time() 119 nextClock = startTime + 1 120 121 while looping: 122 key = None 123 try: 124 key = self.screen.getch() 125 except: 126 pass 127 128 if key == ord ( " " ): 129 looping = False 130 131 if time.time() > nextClock: 132 now = datetime.now() 133 self.bigPrint ( now.strftime ( "%H:%M:%S" ) , ( 1 , (80-56) / 2 ) ) 134 nextClock += 1 135 136 screen = console() 137 screen.loop()
In programming, a class
is a set of variables and functions that represent a bigger unit. You can create instances of classes that all have the same capabilities but operate independently. If a program were an orchestra, a class could represent each musician. Here, the console
class represents a set of functions to display block text on the screen.
The __init__
function is a special function in a class. It is called automatically whenever the class is instantiated (created for the first time). Similar to setup
in the Arduino code, it lets you get the class ready for whatever it needs to do. Here I use it to set up curses.
Line 9 initializes the curses library, and lines 10 and 11 set up how I want curses to function overall. The curses.noecho
function says not to echo (print) characters as they are typed; therefore, any characters drawn to the screen have to be put there explicitly. The curses.cbreak
line tells curses to return any key presses immediately, instead of waiting for the Enter key to be pressed.
Sometimes you also might want to check to see whether a key is pressed and continue whether it is or not. Curses calls this nodelay
. Here, I have to set this mode explicitly on self.screen
, the terminal display. In this program, I'm using the entire terminal as a single window, but curses supports splitting it into smaller windows and controlling them individually, so each can react to keys differently; therefore, I have to tell it explicitly which window I want to place in nodelay
mode.
The self.screen.keypad
line tells curses to interpret escape sequences coming from the terminal automatically. Special keys like the arrow keys, function keys, and Page Up/Down keys usually transmit an escape and then a key code. In keypad
mode, you get a constant instead, which is easier to compare and makes a program more readable.
The rest of the __init__
function defines the block characters that will be displayed in the Big Print clock. Line 15 creates a dictionary of definitions. The self.digits
key (which is
in line 16) specifies the character being stored. Triple quotes start a block string. Until Python encounters another set of triple quotes, everything will be considered a part of the string. Lines 17-21 are a block of lines and characters that define what should be filled in to draw the character identified in the key.
Each set of seven lines defines the numbers 0 through 9 and a colon. I chose the #
character for the blocks, but that choice is arbitrary. The bigPrint
method (lines 94-114) shows why.
Buy Linux Magazine
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters
Support Our Work
Linux Magazine content is made possible with support from readers like you. Please consider contributing when you’ve found an article to be beneficial.
News
-
Halcyon Creates Anti-Ransomware Protection for Linux
As more Linux systems are targeted by ransomware, Halcyon is stepping up its protection.
-
Valve and Arch Linux Announce Collaboration
Valve and Arch have come together for two projects that will have a serious impact on the Linux distribution.
-
Hacker Successfully Runs Linux on a CPU from the Early ‘70s
From the office of "Look what I can do," Dmitry Grinberg was able to get Linux running on a processor that was created in 1971.
-
OSI and LPI Form Strategic Alliance
With a goal of strengthening Linux and open source communities, this new alliance aims to nurture the growth of more highly skilled professionals.
-
Fedora 41 Beta Available with Some Interesting Additions
If you're a Fedora fan, you'll be excited to hear the beta version of the latest release is now available for testing and includes plenty of updates.
-
AlmaLinux Unveils New Hardware Certification Process
The AlmaLinux Hardware Certification Program run by the Certification Special Interest Group (SIG) aims to ensure seamless compatibility between AlmaLinux and a wide range of hardware configurations.
-
Wind River Introduces eLxr Pro Linux Solution
eLxr Pro offers an end-to-end Linux solution backed by expert commercial support.
-
Juno Tab 3 Launches with Ubuntu 24.04
Anyone looking for a full-blown Linux tablet need look no further. Juno has released the Tab 3.
-
New KDE Slimbook Plasma Available for Preorder
Powered by an AMD Ryzen CPU, the latest KDE Slimbook laptop is powerful enough for local AI tasks.
-
Rhino Linux Announces Latest "Quick Update"
If you prefer your Linux distribution to be of the rolling type, Rhino Linux delivers a beautiful and reliable experience.