Skip to content
Merged
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
133 changes: 133 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# Panorama

A telemetry visualization client for robotics sensor data, developed by [UBC Open Robotics](https://ubcopenrobotics.ca). Panorama receives a live JSON stream from an ESP32 (or a mock Python server), and displays it in a GUI with real-time graphs and data logging.

## Table of Contents

- [Architecture](#architecture)
- [Prerequisites](#prerequisites)
- [Build & Run](#build--run)
- [JSON Data Format](#json-data-format)
- [Firmware](#firmware)
- [Project Structure](#project-structure)
- [CI](#ci)

## Architecture

```
ESP32 Firmware Panorama Client (C++ / wxWidgets)
───────────── ──────────────────────────────────
Sensor data → TCP Client → Message Model → GUI / Graphs
(JSON over TCP) (port 3000) DataBuffer Data Logger
```

- **Client** (`client/`) — C++20 desktop app built with wxWidgets and wxMathPlot
- **Firmware** (`firmware/`) — ESP32 Arduino firmware that streams NDJSON sensor data over TCP (port 9000)
- **Tools** (`tools/`) — Python mock server and test utilities for development without hardware

## Prerequisites

### Client

| Platform | Requirements |
|----------|-------------|
| Linux | `cmake >= 3.20`, `g++ (C++20)`, wxWidgets (`wx-config` on PATH) |
| macOS | `cmake >= 3.20`, `clang (C++20)`, wxWidgets (`wx-config` on PATH) |
| Windows | `cmake >= 3.20`, MSVC or MinGW, wxWidgets (set `WXWIN` environment variable) |

**Install wxWidgets (Linux/macOS):**
```bash
# Via the provided build script (recommended):
bash scripts/wxbuild.sh

# ** Other Install Methods ***
# Via Homebrew (macOS):
brew install wxwidgets

# Via apt (Ubuntu/Debian):
sudo apt-get install libwxgtk3.2-dev
```

### Firmware

- [PlatformIO](https://platformio.org/) CLI or IDE extension
- ESP32 dev board

## Build & Run

Source the environment script once per shell session to enable shorthand commands:

```bash
source scripts/env.sh
```

| Command | Description |
|------------|------------------------------------------|
| `build` | Configure and build the project |
| `run` | Build (if needed) and launch the client |
| `clean` | Remove the `build/` directory |
| `runtests` | Build in Release mode and run CTest |

Or use the scripts directly:

```bash
# Build
bash scripts/build.sh [Debug|Release]

# Run (connects to ESP32 at 127.0.0.1:3000)
bash scripts/run.sh

# Run with Python mock server instead of ESP32
bash scripts/run.sh -noesp # OR: -pserver

# Run in headless/console mode
bash scripts/run.sh -nogui

# Run tests
bash scripts/runtests.sh
```

## JSON Data Format

The client expects newline-delimited JSON (NDJSON). Each line is one sensor reading:

```json
{"datatype": "temperature", "data": 29.5, "dataunit": "celsius", "timestamp": 1770504589}
{"datatype": "light", "data": 0.2, "dataunit": "nm", "timestamp": 1770504589}
```

## Firmware

The ESP32 firmware creates a WiFi Access Point and streams sensor data over TCP.

**Flash with PlatformIO:**
```bash
cd firmware/panorama
pio run --target upload
pio device monitor
```

Update `upload_port` and `monitor_port` in `firmware/panorama/platformio.ini` to match your serial port (e.g., `/dev/ttyUSB0` on Linux, `COM3` on Windows).

## Project Structure

```
panorama/
├── client/
│ ├── src/ # C++ source files
│ ├── include/
│ │ ├── client/ # Client-specific headers
│ │ └── common/ # Shared headers (panorama_defines, panorama_utils)
│ └── external/ # wxMathPlot (bundled)
├── firmware/
│ └── panorama/ # PlatformIO ESP32 project
├── scripts/ # Build, run, test, and wxWidgets install scripts
├── tests/ # CTest unit tests
├── tools/ # Python mock server and dev utilities
├── rundir/ # Runtime data directory (config, logs, data)
└── doc/ # Documentation
```

## CI

CI runs on pull requests to `main` via GitHub Actions (Ubuntu). Pull requests prefixed with `WIP` skip CI automatically.
90 changes: 4 additions & 86 deletions client/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ set(WXMATHPLOT_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
add_subdirectory(external/wxMathPlot_0.2.0/mathplot)

# Executable settings
if(WIN32)
add_executable(panorama-client WIN32 ${CLIENT_SOURCES} ${COMMON_HEADERS} ${CLIENT_HEADERS})
elseif(APPLE)
if(APPLE)
add_executable(panorama-client MACOSX_BUNDLE ${CLIENT_SOURCES} ${COMMON_HEADERS} ${CLIENT_HEADERS})
else()
add_executable(panorama-client ${CLIENT_SOURCES} ${COMMON_HEADERS} ${CLIENT_HEADERS})
Expand All @@ -22,15 +20,9 @@ target_include_directories(panorama-client
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include
)

if(WIN32)
target_link_libraries(panorama-client PRIVATE ws2_32)
endif()


# wxWidgets Configuration
if(WIN32)
set(WXWIDGETS_PREBUILT_PATH "$ENV{WXWIN}")
elseif(APPLE)
if(APPLE)
set(WXWIDGETS_PREBUILT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/third_party/wxwidgets-prebuilt/macos-arm64")
else()
set(WXWIDGETS_PREBUILT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/third_party/wxwidgets-prebuilt/linux-x64")
Expand All @@ -40,77 +32,7 @@ set(wxWidgets_ROOT_DIR "${WXWIDGETS_PREBUILT_PATH}")
set(wxWidgets_LIB_DIR "${WXWIDGETS_PREBUILT_PATH}/lib")
set(wxWidgets_INCLUDE_DIR "${WXWIDGETS_PREBUILT_PATH}/include")

if(WIN32)
# Detect if using MinGW or MSVC
if(MINGW)
message(STATUS "Configuring for MinGW build")

target_include_directories(panorama-client PRIVATE
"${wxWidgets_INCLUDE_DIR}"
"${WXWIDGETS_PREBUILT_PATH}/lib/wx/include/x86_64-w64-mingw32-msw-unicode-static-3.3"
)

target_link_libraries(panorama-client PRIVATE
"${wxWidgets_LIB_DIR}/libwx_mswu-3.3-x86_64-w64-mingw32.a"
"${wxWidgets_LIB_DIR}/libwxregexu-3.3-x86_64-w64-mingw32.a"
"${wxWidgets_LIB_DIR}/libwxexpat-3.3-x86_64-w64-mingw32.a"
"${wxWidgets_LIB_DIR}/libwxtiff-3.3-x86_64-w64-mingw32.a"
"${wxWidgets_LIB_DIR}/libwxjpeg-3.3-x86_64-w64-mingw32.a"
"${wxWidgets_LIB_DIR}/libwxpng-3.3-x86_64-w64-mingw32.a"
"${wxWidgets_LIB_DIR}/libwxzlib-3.3-x86_64-w64-mingw32.a"
# Windows system libraries
-lkernel32 -luser32 -lgdi32 -lcomdlg32 -lwinspool -lwinmm -lshell32
-lshlwapi -lcomctl32 -lole32 -loleaut32 -luuid -lrpcrt4 -ladvapi32
-lversion -lws2_32 -lwininet -loleacc -luxtheme
)

target_compile_definitions(panorama-client PRIVATE
__WXMSW__
WXUSINGDLL=0
_UNICODE
UNICODE
)

elseif(MSVC)
message(STATUS "Configuring for MSVC build")

target_include_directories(panorama-client PRIVATE
"${wxWidgets_INCLUDE_DIR}"
"${wxWidgets_LIB_DIR}/vc_x64_lib/mswu"
)

# Find all wxWidgets libraries in the lib directory
file(GLOB WX_LIBS "${wxWidgets_LIB_DIR}/vc_x64_lib/wx*.lib")

if(NOT WX_LIBS)
message(FATAL_ERROR "No wxWidgets libraries found in ${wxWidgets_LIB_DIR}")
endif()

message(STATUS "Found wxWidgets libraries:")
foreach(lib ${WX_LIBS})
get_filename_component(lib_name ${lib} NAME)
message(STATUS " ${lib_name}")
endforeach()

target_link_libraries(panorama-client PRIVATE
${WX_LIBS}
# Windows system libraries
kernel32.lib user32.lib gdi32.lib comdlg32.lib winspool.lib winmm.lib
shell32.lib shlwapi.lib comctl32.lib ole32.lib oleaut32.lib uuid.lib
rpcrt4.lib advapi32.lib version.lib ws2_32.lib wininet.lib oleacc.lib
uxtheme.lib gdiplus.lib
)

target_compile_definitions(panorama-client PRIVATE
__WXMSW__
_UNICODE
UNICODE
)
endif()

message(STATUS "Using wxWidgets prebuilt from ${WXWIDGETS_PREBUILT_PATH}")

elseif(APPLE)
if(APPLE)
# macOS: Use system-installed wxWidgets from /usr/local
find_program(wxWidgets_CONFIG_EXECUTABLE wx-config
HINTS /usr/local/bin
Expand Down Expand Up @@ -183,11 +105,7 @@ else() # linux
message(STATUS "Using locally built wxWidgets via wx-config: ${wxWidgets_CONFIG_EXECUTABLE}")
endif()

if(MSVC)
target_compile_options(panorama-client PRIVATE /W4)
else()
target_compile_options(panorama-client PRIVATE -Wall -Wextra -Wpedantic)
endif()
target_compile_options(panorama-client PRIVATE -Wall -Wextra -Wpedantic)


install(TARGETS panorama-client
Expand Down
4 changes: 0 additions & 4 deletions scripts/example.json

This file was deleted.

Loading