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
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
references/
bin/
vendor/
*.exe
out.wav
ref.wav
23 changes: 23 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
Copyright (c) 2026, marcell
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ LDFLAGS := -X 'github.com/sqpp/flex-golang.Version=$(VERSION)' \
-X 'github.com/sqpp/flex-golang.BuildGoVer=$(GO_VERSION)'

BINARY_NAME=flex-decode
ENCODE_NAME=flex-encode
ifeq ($(OS),Windows_NT)
BINARY_NAME=flex-decode.exe
ENCODE_NAME=flex-encode.exe
endif

# Default target
Expand All @@ -31,12 +33,14 @@ build:
@echo "Building FLEX-GO v$(VERSION)..."
@mkdir -p bin
go build -ldflags "$(LDFLAGS)" -o bin/$(BINARY_NAME) ./cmd/flex-decode
go build -ldflags "$(LDFLAGS)" -o bin/$(ENCODE_NAME) ./cmd/flex-encode
@echo "Build complete!"

# Install tools
.PHONY: install
install:
go install -ldflags "$(LDFLAGS)" ./cmd/flex-decode
go install -ldflags "$(LDFLAGS)" ./cmd/flex-encode

# Test
.PHONY: test
Expand Down
168 changes: 130 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
# FLEX-GO v1.0.0

A complete Go implementation of the Motorola FLEX pager protocol decoder. Inspired by and compatible with the [`pocsag-golang`](https://github.com/sqpp/pocsag-golang) architecture.
A complete Go implementation of the Motorola FLEX pager protocol — encoder, decoder, and everything in between. CLI layout mirrors [`pocsag-golang`](https://github.com/sqpp/pocsag-golang).

## What it can do

- Decode FLEX frames directly from standard WAV audio files (1600/2, 3200/2, 3200/4, 6400/4 modes)
- Handle Frame Information Words (FIW) and Block Information Words (BIW) natively
- Full BCH(31,21) and Motorola 4-bit checksum validation and 2-bit error correction
- Extract Capcode addresses and Alphanumeric/Numeric message payloads
- Pure native Go DPLL demodulator with phase-locking — no CGO, `sox`, or external C tools required
- Encode FLEX messages as WAV audio (1600/2, 1600/4, 3200/2, 3200/4, 6400/4)
- Decode those WAV files back to text with a native PLL demodulator
- BCH(31,21) codewords with Motorola 4-bit checksum validation
- Capcode addressing with alphanumeric, numeric, and tone page types
- Frame Information Words (FIW) and Block Information Words (BIW)
- JSON output for scripting and API integration
- Drop-in CLI replacement perfectly mirroring `pocsag-decode`'s payload format
- Pure Go — no CGO or external demod tools required

---

## Installation

```bash
# Encoder
go install github.com/sqpp/flex-golang/cmd/flex-encode@latest

# Decoder
go install github.com/sqpp/flex-golang/cmd/flex-decode@latest
```
Expand All @@ -26,82 +29,171 @@ Or build from source:
```bash
git clone https://github.com/sqpp/flex-golang.git
cd flex-golang
make build
# Binary lands in: bin/flex-decode
make build # Linux/macOS
make.bat build # Windows
# Binaries land in: bin/flex-encode, bin/flex-decode
```

---

## Encoder (`flex-encode`)

Generate a FLEX message as a WAV file.

**Required (unless using `--reference` or `--messages`):**
- `-a` / `--address` — pager address / capcode (1..2097151)
- `-m` / `--message` — the message text

**Optional:**
- `-o` / `--output` — output WAV file (default: `output.wav`)
- `-f` / `--function` — `2` = tone, `3` = numeric, `5` = alphanumeric (default: `5`)
- `--mode` — FLEX mode: `1600/2`, `1600/4`, `3200/2`, `3200/4`, `6400/4` (default: `1600/2`)
- `--cycle` / `--frame` — FIW cycle and frame fields (optional)
- `--reference` — encode the known-good PDW reference page from `tests/test_1600.wav`
- `--messages` / `--messages-file` — JSON file with a list of message objects
- `-j` / `--json` — print result as JSON instead of human-readable text
- `-v` / `--version` — show version info

**Examples:**

```bash
# Basic message
flex-encode -a 1913 -m "HELLO WORLD" -o message.wav

# Long flags
flex-encode --address 1913 --message "HELLO WORLD" --output message.wav

# Reference page (cap 1913, cycle 3, frame 111)
flex-encode -o reference.wav --reference

# 3200/4 mode
flex-encode -a 1913 -m "FAST MSG" --mode 3200/4 -o fast.wav

# JSON output (great for scripts)
flex-encode -a 1913 -m "TEST" -o test.wav --json
```

**Normal output:**
```
✅ Generated message.wav
Address: 1913, Function: 5, Baud: 1600, Message: HELLO WORLD
Size: 220104 bytes, Duration: 2.50 s

Decode: flex-decode -i message.wav
```

**JSON output:**
```json
{
"success": true,
"output": "message.wav",
"address": 1913,
"function": 5,
"message": "HELLO WORLD",
"baud": 1600,
"type": "alphanumeric",
"size": 220104,
"duration_s": 2.495
}
```

---

## Decoder (`flex-decode`)

Decode a WAV file containing 2-FSK or 4-FSK FLEX audio into messages.
Decode a FLEX WAV back to text.

**Options:**
- `-i` / `--input` — input WAV file (required)
- `-j` / `--json` — print result as JSON instead of human-readable text
- `--no-tones` — filter out tone-only messages
- `-j` / `--json` — JSON output
- `-v` / `--version` — show version info

**Examples:**

```bash
flex-decode -i Flex-1600.wav
flex-decode -i Flex-1600.wav --json
flex-decode -i message.wav
flex-decode -i message.wav --json
flex-decode --version
```

**Normal output:**
```
FLEX-1600/2: Decoded messages:
Address: 1913 Function: 5 ALPHA Message: NEW JOB: BED: B6 ROOM 19 BED 02
Address: 0001913 Function: 5 ALPHA Message: HELLO WORLD
```

**JSON output:**
```json
{
"success": true,
"baud": 1600,
"messages": [
{
"address": 1913,
"function": 5,
"message": "NEW JOB: BED: B6 ROOM 19 BED 02",
"message": "HELLO WORLD",
"type": "alphanumeric"
}
],
"success": true
]
}
```

---

## Using as a Go library

**Encode and write a WAV:**
```go
package main

import (
"fmt"
"os"
flex "github.com/sqpp/flex-golang"
)

func main() {
wavData, _ := os.ReadFile("Flex-1600.wav")

// Decode audio directly into messages via the native DPLL
messages, _ := flex.DecodeFromAudio(wavData)

for _, msg := range messages {
fmt.Printf("Message to %07d: %s\n", msg.Capcode, msg.Text)
}
import flex "github.com/sqpp/flex-golang"

msg := flex.EncodeMessage{Capcode: 1913, Type: "alpha", Text: "HELLO WORLD"}
wav, _, _, err := flex.EncodeToWAVBytes([]flex.EncodeMessage{msg}, flex.Mode1600_2, 0, 0)
os.WriteFile("output.wav", wav, 0644)
```

**Decode a WAV:**
```go
wavData, _ := os.ReadFile("message.wav")
messages, err := flex.DecodeFromAudio(wavData)
for _, msg := range messages {
fmt.Printf("Message to %07d: %s\n", msg.Capcode, msg.Text)
}
```

**Key functions:**

| Function | Description |
|----------|-------------|
| `EncodeToWAVBytes(messages, mode, cycle, frame)` | Encode messages to WAV bytes |
| `EncodeToWAVFile(messages, path, mode, cycle, frame)` | Encode and write a WAV file |
| `DecodeFromAudio(wavData)` | Decode a WAV into messages |
| `DemodulateRawFrames(wavData)` | Demodulate to raw phase codewords |
| `EncodeModeNames()` | List supported encoder modes |
| `EncodeModeBitRate(mode)` | On-air bit rate for a mode name |

---

## Roadmap
## Testing

- **Encoder (`flex`)**: Generate FLEX WAV signals (Pending)
- **Burst Encoder (`flex-burst`)**: Pack multiple messages into single frames (Pending)
```bash
go test -v ./...
```

Tests live in the `tests/` directory. Reference captures (`test_1600.wav`, `test_3200.wav`, `test_6400.wav`) are included for roundtrip and decode validation.

---

## About addresses

FLEX addresses are capcodes in the range 1..2097151. The encoder and decoder use the full capcode value. Function codes follow the FLEX vector type field (`2` = tone, `3` = numeric, `5` = alphanumeric).

---

## Credits

Part of [PagerCast](https://pagercast.com). Decoder logic references multimon-ng `demod_flex.c` and PDW `Flex.cpp`.

## License
Built natively for pure-Go portability. Decoding architecture originally inspired by PDW and multimon-ng.

BSD-2-Clause — see [LICENSE](LICENSE).
Loading
Loading