Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,20 @@ compile_commands.json
*creator.user*

*_qmlcache.qrc

# CMake build directories
build/
Build/
cmake-build-*/
CMakeCache.txt
CMakeFiles/
cmake_install.cmake
install_manifest.txt

# CodeQL build artifacts
_codeql_build_dir/
_codeql_detected_source_root

# Executables
SerialChat
SerialChat.exe
56 changes: 56 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
cmake_minimum_required(VERSION 3.5)

project(SerialChat VERSION 1.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Automatically handle Qt MOC, UIC, and RCC
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)

# Find Qt5 packages (minimum version 5.12)
find_package(Qt5 5.12 REQUIRED COMPONENTS Core Gui Widgets SerialPort)

# Source files
set(SOURCES
src/main.cpp
src/models/SerialPort.cpp
src/models/Message.cpp
src/models/Session.cpp
src/views/MainWindow.cpp
src/views/MessageBubbleWidget.cpp
)

# Header files
set(HEADERS
src/models/SerialPort.h
src/models/Message.h
src/models/Session.h
src/views/MainWindow.h
src/views/MessageBubbleWidget.h
)

# Create executable
add_executable(${PROJECT_NAME} ${SOURCES} ${HEADERS})

# Link Qt5 libraries
target_link_libraries(${PROJECT_NAME}
Qt5::Core
Qt5::Gui
Qt5::Widgets
Qt5::SerialPort
)

# Include directories
target_include_directories(${PROJECT_NAME} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src
${CMAKE_CURRENT_SOURCE_DIR}/src/models
${CMAKE_CURRENT_SOURCE_DIR}/src/views
)

# Platform-specific settings for Qt6 compatibility
if(WIN32)
set_target_properties(${PROJECT_NAME} PROPERTIES WIN32_EXECUTABLE TRUE)
endif()
78 changes: 77 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,78 @@
# Serial-Chat
​​Serial Chat​​: Reimagining serial port debugging as an intuitive chat conversation. Visualize data flows between devices and applications in a familiar, collaborative interface.
**Serial Chat**: Reimagining serial port debugging as an intuitive chat conversation. Visualize data flows between devices and applications in a familiar, collaborative interface.

## Features
- Qt5-based C++ application (compatible with Qt 5.12+)
- Clean MVC architecture with separation between models and views
- Real-time serial port communication
- Chat-like interface for visualizing incoming/outgoing data
- Support for multiple baud rates and serial port configurations

## Architecture

The project follows a clean Model-View separation:

### Models (`src/models/`)
- **SerialPort**: Wrapper around QSerialPort for managing serial port connections
- Signals: `dataReceived()`, `errorOccurred()`, `connectionStatusChanged()`
- Methods: `openPort()`, `closePort()`, `writeData()`, `readData()`

- **Message**: Represents a single message with content, direction (incoming/outgoing), and timestamp
- Properties: `content`, `direction`, `timestamp`
- Signals: `contentChanged()`, `directionChanged()`

- **Session**: Manages a collection of messages
- Methods: `addMessage()`, `clearMessages()`, `messages()`
- Signals: `messageAdded()`, `messagesCleared()`

### Views (`src/views/`)
- **MainWindow**: Main application window with sidebar and chat area
- Sidebar: Port selection, baud rate configuration, connection controls
- Chat Area: QScrollArea displaying message bubbles
- Input: Text field and send button for outgoing messages

- **MessageBubbleWidget**: Visual representation of a message as a chat bubble
- Different styling for incoming (white) vs outgoing (green) messages
- Displays message content and timestamp

## Building the Project

### Prerequisites
- CMake 3.5 or higher
- Qt5 5.12 or higher (Core, Gui, Widgets, SerialPort modules)
- C++11 compatible compiler

### Linux (Ubuntu/Debian)
```bash
# Install dependencies
sudo apt-get update
sudo apt-get install -y qtbase5-dev libqt5serialport5-dev cmake build-essential

# Build
mkdir build
cd build
cmake ..
cmake --build .

# Run
./SerialChat
```

### Future Qt6 Compatibility
The code is structured to be portable to Qt6 with minimal changes:
- Uses `find_package(Qt5 ...)` which can be changed to `find_package(Qt6 ...)`
- Uses Qt5 signal/slot syntax compatible with Qt6
- No deprecated Qt5 APIs used
- Clean separation of concerns makes migration easier

## Usage
1. Launch the application
2. Select a serial port from the dropdown (click "Refresh Ports" to update the list)
3. Choose the appropriate baud rate
4. Click "Connect" to establish the connection
5. Type messages in the input field and press "Send" or Enter
6. Incoming data appears as white bubbles on the left
7. Outgoing data appears as green bubbles on the right

## License
See LICENSE file for details.
16 changes: 16 additions & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#include <QApplication>
#include "MainWindow.h"

int main(int argc, char *argv[])
{
QApplication app(argc, argv);

app.setApplicationName("Serial Chat");
app.setApplicationVersion("1.0");
app.setOrganizationName("SerialChat");

MainWindow mainWindow;
mainWindow.show();

return app.exec();
}
52 changes: 52 additions & 0 deletions src/models/Message.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#include "Message.h"

Message::Message(QObject *parent)
: QObject(parent)
, m_direction(Incoming)
, m_timestamp(QDateTime::currentDateTime())
{
}

Message::Message(const QString &content, Direction direction, QObject *parent)
: QObject(parent)
, m_content(content)
, m_direction(direction)
, m_timestamp(QDateTime::currentDateTime())
{
}

QString Message::content() const
{
return m_content;
}

void Message::setContent(const QString &content)
{
if (m_content != content) {
m_content = content;
emit contentChanged();
}
}

Message::Direction Message::direction() const
{
return m_direction;
}

void Message::setDirection(Direction direction)
{
if (m_direction != direction) {
m_direction = direction;
emit directionChanged();
}
}

QDateTime Message::timestamp() const
{
return m_timestamp;
}

void Message::setTimestamp(const QDateTime &timestamp)
{
m_timestamp = timestamp;
}
41 changes: 41 additions & 0 deletions src/models/Message.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#ifndef MESSAGE_H
#define MESSAGE_H

#include <QString>
#include <QDateTime>
#include <QObject>

class Message : public QObject
{
Q_OBJECT

public:
enum Direction {
Incoming,
Outgoing
};

explicit Message(QObject *parent = nullptr);
Message(const QString &content, Direction direction, QObject *parent = nullptr);
~Message() override = default;

QString content() const;
void setContent(const QString &content);

Direction direction() const;
void setDirection(Direction direction);

QDateTime timestamp() const;
void setTimestamp(const QDateTime &timestamp);

signals:
void contentChanged();
void directionChanged();

private:
QString m_content;
Direction m_direction;
QDateTime m_timestamp;
};

#endif // MESSAGE_H
102 changes: 102 additions & 0 deletions src/models/SerialPort.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
#include "SerialPort.h"

SerialPort::SerialPort(QObject *parent)
: QObject(parent)
, m_serialPort(new QSerialPort(this))
, m_baudRate(QSerialPort::Baud9600)
{
connect(m_serialPort, &QSerialPort::readyRead, this, &SerialPort::handleReadyRead);
connect(m_serialPort, &QSerialPort::errorOccurred, this, &SerialPort::handleError);
}

SerialPort::~SerialPort()
{
closePort();
}

bool SerialPort::openPort(const QString &portName, qint32 baudRate)
{
closePort();

m_portName = portName;
m_baudRate = baudRate;

m_serialPort->setPortName(portName);
m_serialPort->setBaudRate(baudRate);
m_serialPort->setDataBits(QSerialPort::Data8);
m_serialPort->setParity(QSerialPort::NoParity);
m_serialPort->setStopBits(QSerialPort::OneStop);
m_serialPort->setFlowControl(QSerialPort::NoFlowControl);

bool opened = m_serialPort->open(QIODevice::ReadWrite);
emit connectionStatusChanged(opened);

return opened;
}

void SerialPort::closePort()
{
if (m_serialPort->isOpen()) {
m_serialPort->close();
emit connectionStatusChanged(false);
}
}

bool SerialPort::isOpen() const
{
return m_serialPort->isOpen();
}

bool SerialPort::writeData(const QByteArray &data)
{
if (!m_serialPort->isOpen()) {
return false;
}

qint64 bytesWritten = m_serialPort->write(data);
if (bytesWritten == -1) {
return false;
}

// Ensure all data is flushed
return m_serialPort->waitForBytesWritten(1000);
}

QByteArray SerialPort::readData()
{
if (!m_serialPort->isOpen()) {
return QByteArray();
}

return m_serialPort->readAll();
}

QString SerialPort::portName() const
{
return m_portName;
}

qint32 SerialPort::baudRate() const
{
return m_baudRate;
}

QList<QSerialPortInfo> SerialPort::availablePorts()
{
return QSerialPortInfo::availablePorts();
}

void SerialPort::handleReadyRead()
{
QByteArray data = m_serialPort->readAll();
if (!data.isEmpty()) {
emit dataReceived(data);
}
}

void SerialPort::handleError(QSerialPort::SerialPortError error)
{
if (error != QSerialPort::NoError && error != QSerialPort::TimeoutError) {
emit errorOccurred(m_serialPort->errorString());
}
}
Loading