diff options
| author | info@mode42.com <info@mode42.com> | 2026-08-07 18:25:13 +0000 |
|---|---|---|
| committer | info@mode42.com <info@mode42.com> | 2026-08-07 18:25:13 +0000 |
| commit | 04d965d67a7264a1c7c211494aebda1953df7603 (patch) | |
| tree | 0ebd700a6e219f84a26a656f4bee8778bc75da7b /stacks/crdop/scripts | |
Initial push
Diffstat (limited to 'stacks/crdop/scripts')
| -rwxr-xr-x | stacks/crdop/scripts/apply-vendor-patches.sh | 36 | ||||
| -rwxr-xr-x | stacks/crdop/scripts/build-crdop.sh | 48 | ||||
| -rwxr-xr-x | stacks/crdop/scripts/crdopc | 171 | ||||
| -rwxr-xr-x | stacks/crdop/scripts/import-ardopcf.sh | 17 | ||||
| -rwxr-xr-x | stacks/crdop/scripts/install-crdop.sh | 16 | ||||
| -rwxr-xr-x | stacks/crdop/scripts/refresh-vendor-ardopcf.sh | 26 | ||||
| -rwxr-xr-x | stacks/crdop/scripts/sync-upstream.sh | 21 | ||||
| -rwxr-xr-x | stacks/crdop/scripts/test-all.sh | 17 | ||||
| -rwxr-xr-x | stacks/crdop/scripts/test-smoke.sh | 24 | ||||
| -rwxr-xr-x | stacks/crdop/scripts/test-with-host.sh | 9 |
10 files changed, 385 insertions, 0 deletions
diff --git a/stacks/crdop/scripts/apply-vendor-patches.sh b/stacks/crdop/scripts/apply-vendor-patches.sh new file mode 100755 index 0000000..da62bf5 --- /dev/null +++ b/stacks/crdop/scripts/apply-vendor-patches.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +# Apply CRDOP patch series — only if vendor tree is still pristine upstream. +# Released CRDOP commits ship vendor/ardopcf with patches already integrated. +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +VENDOR="${ROOT}/vendor/ardopcf" +PATCH_DIR="${ROOT}/patches/ardopcf" +STAMP="${VENDOR}/.crdop-patches-applied" +MARKER="${VENDOR}/src/common/ARDOPC.c" + +if [[ ! -d "${VENDOR}/src" ]]; then + echo "error: ${VENDOR} missing" >&2 + exit 1 +fi + +if grep -q 'CRDOP src/crdop_version.c' "${MARKER}" 2>/dev/null; then + date -u +'%Y-%m-%dT%H:%M:%SZ' > "${STAMP}" + echo "CRDOP vendor already integrated ($(cat "${STAMP}"))" + exit 0 +fi + +if [[ ! -d "${PATCH_DIR}" ]] || ! compgen -G "${PATCH_DIR}/*.patch" >/dev/null; then + echo "error: no patches in ${PATCH_DIR}" >&2 + exit 1 +fi + +cd "${VENDOR}" +for p in "${PATCH_DIR}"/*.patch; do + echo "applying ${p#${ROOT}/}" + git apply --check "${p}" 2>/dev/null || patch -p1 --forward -r - < "${p}" + git apply "${p}" 2>/dev/null || patch -p1 --forward < "${p}" +done + +date -u +'%Y-%m-%dT%H:%M:%SZ' > "${STAMP}" +echo "CRDOP patches applied ($(cat "${STAMP}"))" diff --git a/stacks/crdop/scripts/build-crdop.sh b/stacks/crdop/scripts/build-crdop.sh new file mode 100755 index 0000000..2253d5a --- /dev/null +++ b/stacks/crdop/scripts/build-crdop.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +# CRDOP unified build — vendor embedded; GCC, Clang, or MSVC via CMake. +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +BUILD="${CRDOP_BUILD_DIR:-${ROOT}/build}" +TYPE="${CRDOP_BUILD_TYPE:-Release}" +TOOLCHAIN="${CRDOP_TOOLCHAIN:-}" +BUILD_TESTS="${CRDOP_BUILD_TESTS:-OFF}" +RUN_TESTS="${CRDOP_RUN_TESTS:-OFF}" + +if [[ -f "${BUILD}/CMakeCache.txt" ]]; then + cached_home="$(grep -m1 '^CMAKE_HOME_DIRECTORY:INTERNAL=' "${BUILD}/CMakeCache.txt" 2>/dev/null | cut -d= -f2- || true)" + if [[ -n "${cached_home}" && "${cached_home}" != "${ROOT}" ]]; then + echo "CRDOP: removing stale CMake cache (was ${cached_home}, now ${ROOT})" >&2 + rm -rf "${BUILD}" + fi +fi + +"${ROOT}/scripts/import-ardopcf.sh" + +CMAKE_ARGS=(-B "${BUILD}" -DCMAKE_BUILD_TYPE="${TYPE}" -DCRDOP_BUILD_TESTS="${BUILD_TESTS}") + +if [[ -n "${TOOLCHAIN}" ]]; then + CMAKE_ARGS+=(-DCMAKE_TOOLCHAIN_FILE="${ROOT}/cmake/toolchains/${TOOLCHAIN}.cmake") +fi + +if [[ -n "${CC:-}" ]]; then + CMAKE_ARGS+=(-DCMAKE_C_COMPILER="${CC}") +fi +if [[ -n "${CXX:-}" ]]; then + CMAKE_ARGS+=(-DCMAKE_CXX_COMPILER="${CXX}") +fi + +cmake "${ROOT}" "${CMAKE_ARGS[@]}" +cmake --build "${BUILD}" -j"$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)" + +"${ROOT}/scripts/test-smoke.sh" + +if [[ "${BUILD_TESTS}" == "ON" || "${BUILD_TESTS}" == "1" || "${RUN_TESTS}" == "ON" || "${RUN_TESTS}" == "1" ]]; then + if [[ -f "${BUILD}/CTestTestfile.cmake" ]]; then + ctest --test-dir "${BUILD}" --output-on-failure + else + echo "NOTE: CRDOP_BUILD_TESTS requested but no CTest targets (install libcmocka-dev?)" >&2 + fi +fi + +echo "CRDOP $(cat "${ROOT}/VERSION") built: ${BUILD}/crdopc" diff --git a/stacks/crdop/scripts/crdopc b/stacks/crdop/scripts/crdopc new file mode 100755 index 0000000..2fc82bd --- /dev/null +++ b/stacks/crdop/scripts/crdopc @@ -0,0 +1,171 @@ +#!/usr/bin/env bash +# CRDOP launcher — native MAX25-SoftModem (audio-dummyd / M25 host). +# ARDOP is a separate optional MAX25-Stack plugin — not part of CRDOP. +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" +MAX25_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" +PREFIX_SHARE="${SCRIPT_DIR}/../share/crdop" + +if [[ -n "${CRDOP_INI:-}" ]]; then + INI="${CRDOP_INI}" +elif [[ -f "${HOME}/.config/crdop/crdop.ini" ]]; then + INI="${HOME}/.config/crdop/crdop.ini" +elif [[ -f "${PREFIX_SHARE}/crdop.ini.example" ]]; then + INI="${PREFIX_SHARE}/crdop.ini.example" +else + INI="${ROOT}/share/crdop.ini.example" +fi + +ini_get() { + local section="$1" key="$2" default="$3" file="$4" + [[ -f "${file}" ]] || { echo "${default}"; return; } + awk -F= -v sec="[${section}]" -v k="${key}" -v d="${default}" ' + $0 ~ /^[[:space:]]*[#;]/ { next } + $1 ~ /^\[/ { cur=$0; gsub(/[][]/, "", cur); next } + cur == substr(sec, 2, length(sec)-2) { + gsub(/^[[:space:]]+|[[:space:]]+$/, "", $1) + if ($1 == k) { + v=$2; gsub(/^[[:space:]]+|[[:space:]]+$/, "", v); gsub(/^"|"$/, "", v) + print v; found=1; exit + } + } + END { if (!found) print d } + ' "${file}" +} + +resolve_ini() { + if [[ ! -f "${INI}" && -f "${ROOT}/share/crdop.ini.example" ]]; then + INI="${ROOT}/share/crdop.ini.example" + fi +} + +normalize_profile() { + case "${1,,}" in + cb|dual|amateur) echo "${1,,}" ;; + *) + echo "warn: unknown radio_profile '${1}', using cb" >&2 + echo "cb" + ;; + esac +} + +normalize_duplex() { + case "${1,,}" in + full|half) echo "${1,,}" ;; + *) + echo "warn: unknown duplex '${1}', using half" >&2 + echo "half" + ;; + esac +} + +enforce_host_audio() { + local backend + backend="$(ini_get audio backend "" "${INI}")" + if [[ -z "${backend}" ]]; then + case "$(uname -s)" in + FreeBSD) backend="oss" ;; + *) backend="alsa" ;; + esac + fi + if [[ "${backend}" == "alsa" ]]; then + local no_pulse + no_pulse="$(ini_get audio no_pulse yes "${INI}")" + case "${no_pulse,,}" in + 1|yes|true|on) + unset PULSE_SERVER PULSE_COOKIE PIPEWIRE_RUNTIME_DIR 2>/dev/null || true + export PULSE_SERVER= + export ALSA_CARD="${ALSA_CARD:-}" + ;; + esac + fi +} + +audio_cli_args() { + local cap pb backend + backend="$(ini_get audio backend "" "${INI}")" + if [[ -z "${backend}" ]]; then + case "$(uname -s)" in + FreeBSD) backend="oss" ;; + *) backend="alsa" ;; + esac + fi + cap="$(ini_get audio capture "" "${INI}")" + pb="$(ini_get audio playback "" "${INI}")" + local args=() + [[ -n "${cap}" ]] && args+=("${cap}") + [[ -n "${pb}" ]] && args+=("${pb}") + if [[ ${#args[@]} -gt 0 ]]; then + if [[ "${backend}" == "alsa" ]]; then + for a in "${args[@]}"; do + if [[ "${a}" == *pulse* || "${a}" == *pipewire* || "${a}" == default ]]; then + echo "error: audio device '${a}' — use hw:/plughw: kernel ALSA" >&2 + exit 1 + fi + done + fi + printf '%s\n' "${args[@]}" + elif [[ "${backend}" == "oss" ]]; then + echo "/dev/dsp" + echo "/dev/dsp" + fi +} + +find_audio_dummyd() { + for candidate in \ + "${MAX25_ROOT}/build/bin/audio-dummyd" \ + "${MAX25_ROOT}/stacks/crdop/tools/audio-dummyd" \ + "$(command -v audio-dummyd 2>/dev/null || true)"; do + [[ -n "${candidate}" && -x "${candidate}" ]] || continue + echo "${candidate}" + return + done + return 1 +} + +resolve_ini + +if [[ -f "${PREFIX_SHARE}/VERSION" ]]; then + CRDOP_VERSION="$(tr -d '[:space:]' < "${PREFIX_SHARE}/VERSION")" +elif [[ -f "${ROOT}/VERSION" ]]; then + CRDOP_VERSION="$(tr -d '[:space:]' < "${ROOT}/VERSION")" +else + CRDOP_VERSION="CUR999" +fi + +PROFILE="$(normalize_profile "$(ini_get profile radio_profile cb "${INI}")")" +DUPLEX="$(normalize_duplex "$(ini_get modem duplex half "${INI}")")" +MYCALL="$(ini_get mycall call NOCALL-0 "${INI}")" +HOST_PORT="$(ini_get host port 8515 "${INI}")" +DATA_PORT=$((HOST_PORT + 1)) +SAMPLE_RATE="$(ini_get audio sample_rate 48000 "${INI}")" + +if [[ $# -ge 1 && "${1}" =~ ^[0-9]+$ ]]; then + HOST_PORT="$1" + DATA_PORT=$((HOST_PORT + 1)) + shift +fi + +mapfile -t AUDIO_ARGS < <(audio_cli_args || true) +enforce_host_audio + +DUMMY="$(find_audio_dummyd || true)" +if [[ -z "${DUMMY}" ]]; then + echo "error: audio-dummyd not found — build MAX25 or set path" >&2 + exit 1 +fi + +DUMMY_ARGS=(--ctrl-port "${HOST_PORT}" --data-port "${DATA_PORT}" -r "${SAMPLE_RATE}") +if [[ ${#AUDIO_ARGS[@]} -ge 1 ]]; then + DUMMY_ARGS+=(-D "${AUDIO_ARGS[0]}") +fi +if [[ ${#AUDIO_ARGS[@]} -ge 2 ]]; then + DUMMY_ARGS+=(-P "${AUDIO_ARGS[1]}") +fi + +AUDIO_BACKEND="$(ini_get audio backend "" "${INI}")" +[[ -z "${AUDIO_BACKEND}" ]] && case "$(uname -s)" in FreeBSD) AUDIO_BACKEND="oss" ;; *) AUDIO_BACKEND="alsa" ;; esac +echo "CRDOP ${CRDOP_VERSION} profile=${PROFILE} duplex=${DUPLEX} ctrl=:${HOST_PORT} data=:${DATA_PORT} call=${MYCALL} audio=${AUDIO_BACKEND} (native M25)" +exec "${DUMMY}" "${DUMMY_ARGS[@]}" "$@" diff --git a/stacks/crdop/scripts/import-ardopcf.sh b/stacks/crdop/scripts/import-ardopcf.sh new file mode 100755 index 0000000..26ac003 --- /dev/null +++ b/stacks/crdop/scripts/import-ardopcf.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# Verify vendor/ardopcf is present (embedded in repo — no submodule/download). +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +VENDOR="${ROOT}/vendor/ardopcf" + +if [[ ! -f "${VENDOR}/LICENSE" || ! -f "${VENDOR}/src/common/ARDOPC.c" ]]; then + echo "error: vendor/ardopcf missing — clone CRDOP with full tree or run scripts/refresh-vendor-ardopcf.sh" >&2 + exit 1 +fi + +if ! grep -q 'CRDOP src/crdop_version.c' "${VENDOR}/src/common/ARDOPC.c" 2>/dev/null; then + echo "warn: vendor/ardopcf may need CRDOP patches — run scripts/apply-vendor-patches.sh" >&2 +fi + +echo "vendor/ardopcf OK (embedded, commit $(grep '^commit=' "${ROOT}/vendor/ardopcf.ref" 2>/dev/null | cut -d= -f2 || echo unknown))" diff --git a/stacks/crdop/scripts/install-crdop.sh b/stacks/crdop/scripts/install-crdop.sh new file mode 100755 index 0000000..23f631c --- /dev/null +++ b/stacks/crdop/scripts/install-crdop.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# Build and install CRDOP to PREFIX (default /usr/local). +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +PREFIX="${CRDOP_PREFIX:-/usr/local}" +BUILD="${CRDOP_BUILD_DIR:-${ROOT}/build}" + +"${ROOT}/scripts/build-crdop.sh" + +cmake --install "${BUILD}" --prefix "${PREFIX}" + +echo "Installed:" +echo " ${PREFIX}/bin/crdopc — modem binary" +echo " ${PREFIX}/bin/crdop — profile launcher" +echo " ${PREFIX}/share/crdop/ — INI examples, VERSION" diff --git a/stacks/crdop/scripts/refresh-vendor-ardopcf.sh b/stacks/crdop/scripts/refresh-vendor-ardopcf.sh new file mode 100755 index 0000000..b9d2f93 --- /dev/null +++ b/stacks/crdop/scripts/refresh-vendor-ardopcf.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# Maintainer tool: re-download upstream ardopcf and re-apply CRDOP patches. +# Normal builds use the committed vendor/ardopcf tree — no network required. +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +VENDOR="${ROOT}/vendor/ardopcf" +REF_FILE="${ROOT}/vendor/ardopcf.ref" +URL="${CRDOP_UPSTREAM_URL:-https://github.com/pflarue/ardop.git}" +PIN="$(grep '^commit_full=' "${REF_FILE}" 2>/dev/null | cut -d= -f2- | head -1)" +PIN="${PIN:-$(grep '^commit=' "${REF_FILE}" 2>/dev/null | cut -d= -f2- | head -1)}" + +echo "This replaces vendor/ardopcf from upstream. Press Ctrl+C to abort." +sleep 2 + +rm -rf "${VENDOR}" +git clone --depth 1 --branch master "${URL}" "${VENDOR}" +if [[ -n "${PIN}" ]]; then + git -C "${VENDOR}" fetch --depth 1 origin "${PIN}" + git -C "${VENDOR}" checkout "${PIN}" +fi +rm -rf "${VENDOR}/.git" +rm -f "${VENDOR}/.crdop-patches-applied" + +"${ROOT}/scripts/apply-vendor-patches.sh" +echo "Done. Review diff, update vendor/ardopcf.ref if needed, commit vendor/ardopcf/" diff --git a/stacks/crdop/scripts/sync-upstream.sh b/stacks/crdop/scripts/sync-upstream.sh new file mode 100755 index 0000000..a1353ad --- /dev/null +++ b/stacks/crdop/scripts/sync-upstream.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +# Test whether CRDOP patches still apply to fresh upstream (maintainers). +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +TMP="$(mktemp -d)" +trap 'rm -rf "${TMP}"' EXIT + +REF="${ROOT}/vendor/ardopcf.ref" +PIN="$(grep '^commit_full=' "${REF}" | cut -d= -f2-)" +URL="https://github.com/pflarue/ardop.git" + +git clone --depth 1 "${URL}" "${TMP}/ardopcf" +git -C "${TMP}/ardopcf" fetch --depth 1 origin "${PIN}" +git -C "${TMP}/ardopcf" checkout "${PIN}" + +for p in "${ROOT}"/patches/ardopcf/*.patch; do + git -C "${TMP}/ardopcf" apply --check "${p}" +done + +echo "OK: patches apply to upstream ${PIN}" diff --git a/stacks/crdop/scripts/test-all.sh b/stacks/crdop/scripts/test-all.sh new file mode 100755 index 0000000..f12181e --- /dev/null +++ b/stacks/crdop/scripts/test-all.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# Smoke test + optional upstream cmocka unit tests (CTest). +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +BUILD="${CRDOP_BUILD_DIR:-${ROOT}/build}" + +export CRDOP_BUILD_TESTS="${CRDOP_BUILD_TESTS:-ON}" +export CRDOP_RUN_TESTS="${CRDOP_RUN_TESTS:-ON}" + +"${ROOT}/scripts/build-crdop.sh" + +if [[ ! -f "${BUILD}/CTestTestfile.cmake" ]]; then + echo "NOTE: unit tests not built (libcmocka missing or unsupported platform)" >&2 +fi + +echo "OK: CRDOP test-all finished" diff --git a/stacks/crdop/scripts/test-smoke.sh b/stacks/crdop/scripts/test-smoke.sh new file mode 100755 index 0000000..6ee6eff --- /dev/null +++ b/stacks/crdop/scripts/test-smoke.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +REPO="$(cd "${ROOT}/../.." && pwd)" +BIN="${CRDOP_BIN:-${ROOT}/build/crdopc}" + +if [[ ! -x "${BIN}" ]]; then + for alt in "${REPO}/build/bin/crdopc" "${REPO}/build/crdop/crdopc"; do + if [[ -x "${alt}" ]]; then + BIN="${alt}" + break + fi + done +fi +VER="$(tr -d '[:space:]' < "${ROOT}/VERSION")" + +[[ -x "${BIN}" ]] || { echo "FAIL: ${BIN} missing" >&2; exit 1; } + +OUT="$("${BIN}" -h 2>&1 || true)" +echo "${OUT}" | grep -qi crdopc || { echo "FAIL: crdopc branding"; exit 1; } +echo "${OUT}" | grep -q "${VER}" || { echo "FAIL: expected version ${VER}"; exit 1; } + +echo "OK: CRDOP ${VER} smoke test passed" diff --git a/stacks/crdop/scripts/test-with-host.sh b/stacks/crdop/scripts/test-with-host.sh new file mode 100755 index 0000000..f14baac --- /dev/null +++ b/stacks/crdop/scripts/test-with-host.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +# P1 — loopback host integration (placeholder). +set -euo pipefail + +echo "Host loopback test not automated yet." +echo " 1. ./scripts/crdopc" +echo " 2. Connect M25/KISS host client to 127.0.0.1:8515" +echo " 3. INITIALIZE / PROTOCOLMODE KISS / LISTEN — verify STATUS ready" +exit 0 |
