Skip to content

Commit

Permalink
Proof of concept
Browse files Browse the repository at this point in the history
  • Loading branch information
redfast00 committed Dec 19, 2020
1 parent bbedf3b commit a8e5afb
Show file tree
Hide file tree
Showing 13 changed files with 398 additions and 9 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
config/config.json
3 changes: 0 additions & 3 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
[submodule "src/md380tools"]
path = src/md380tools
url = [email protected]:travisgoodspeed/md380tools.git
[submodule "src/callrec"]
path = src/callrec
url = [email protected]:BrandMeister/callrec.git
55 changes: 50 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,53 @@
FROM debian:stretch
#
# callrec builder
#
FROM golang AS callrecbuilder

RUN apt-get update && apt-get install -y \
build-essential \
gcc-arm-linux-gnueabi \
WORKDIR /go
RUN go get github.com/BrandMeister/callrec
RUN go build github.com/BrandMeister/callrec


#
# MD380 builder
#
FROM debian:stretch AS md380builder

RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential gcc-arm-linux-gnueabi unzip curl libc6-armel-cross libc6-dev-armel-cross ca-certificates python

WORKDIR /md380tools
COPY src/md380tools .

WORKDIR /md380tools/emulator
RUN make md380-emu

#
# Running container
#

FROM ruby:2.7-buster

RUN apt-get update && apt-get install -y --no-install-recommends \
sox \
wget \
mplayer
mplayer \
python \
python3 \
python-pip python-setuptools python-dev python-wheel \
qemu-user \
libopus-dev \
nano

RUN pip install bitarray bitstring
RUN gem install mumble-ruby

WORKDIR /app
COPY --from=callrecbuilder /go/callrec .
COPY --from=md380builder /md380tools/emulator/md380-emu .

COPY src .
COPY config .


CMD ./start.sh
2 changes: 2 additions & 0 deletions build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/bin/bash
docker build -t dmr .
Empty file added config/certs/.gitkeep
Empty file.
25 changes: 25 additions & 0 deletions config/config-example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"serverHost": "master.ham-dmr.be",
"serverPort": 54005,
"serverPassword": "<insert password here>",
"serverTimeoutSeconds": 12,
"appID": 123456,
"recTalkgroupID": 91,
"callHangTimeSeconds": 3,
"callExecCommand1": "tee talk.log",
"callExecCommand1ShowStderr": false,
"callExecCommand2": "tee /tmp/dmr.fifo",
"callExecCommand2ShowStderr": false,
"callExecCommand3": "",
"callExecCommand3ShowStderr": false,
"outputDir": "",
"outputFileExtension": "ambe",
"createDailyAggregateFile": true,
"mumble" : {
"server": "mumble.example.com",
"username": "DMR-bot",
"password": "<mumble password>",
"channel": "amateurradio",
"fifo": "/tmp/audio.fifo"
}
}
1 change: 0 additions & 1 deletion src/callrec
Submodule callrec deleted from 6b2665
237 changes: 237 additions & 0 deletions src/decode72to49.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
#!/usr/bin/env python2

from binascii import b2a_hex as ahex
import sys
from bitarray import bitarray
from bitstring import BitArray
from bitstring import BitString
from datetime import datetime

##
# DMR AMBE interleave schedule
##
rW = [
0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 2,
0, 2, 0, 2, 0, 2,
0, 2, 0, 2, 0, 2
]

rX = [
23, 10, 22, 9, 21, 8,
20, 7, 19, 6, 18, 5,
17, 4, 16, 3, 15, 2,
14, 1, 13, 0, 12, 10,
11, 9, 10, 8, 9, 7,
8, 6, 7, 5, 6, 4
]

rY = [
0, 2, 0, 2, 0, 2,
0, 2, 0, 3, 0, 3,
1, 3, 1, 3, 1, 3,
1, 3, 1, 3, 1, 3,
1, 3, 1, 3, 1, 3,
1, 3, 1, 3, 1, 3
]

rZ = [
5, 3, 4, 2, 3, 1,
2, 0, 1, 13, 0, 12,
22, 11, 21, 10, 20, 9,
19, 8, 18, 7, 17, 6,
16, 5, 15, 4, 14, 3,
13, 2, 12, 1, 11, 0
]


# This function calculates [23,12] Golay codewords.
# The format of the returned longint is [checkbits(11),data(12)].
def golay2312(cw):
POLY = 0xAE3 #/* or use the other polynomial, 0xC75 */
cw = cw & 0xfff # Strip off check bits and only use data
c = cw #/* save original codeword */
for i in range(1,13): #/* examine each data bit */
if (cw & 1): #/* test data bit */
cw = cw ^ POLY #/* XOR polynomial */
cw = cw >> 1 #/* shift intermediate result */
return((cw << 12) | c) #/* assemble codeword */

# This function checks the overall parity of codeword cw.
# If parity is even, 0 is returned, else 1.
def parity(cw):
#/* XOR the bytes of the codeword */
p = cw & 0xff
p = p ^ ((cw >> 8) & 0xff)
p = p ^ ((cw >> 16) & 0xff)

#/* XOR the halves of the intermediate result */
p = p ^ (p >> 4)
p = p ^ (p >> 2)
p = p ^ (p >> 1)

#/* return the parity result */
return(p & 1)

# Demodulate ambe frame (C1)
# Frame is an array [4][24]
def demodulateAmbe3600x2450(ambe_fr):
pr = [0] * 115
foo = 0

# create pseudo-random modulator
for i in range(23, 11, -1):
foo = foo << 1
foo = foo | ambe_fr[0][i]
pr[0] = (16 * foo)
for i in range(1, 24):
pr[i] = (173 * pr[i - 1]) + 13849 - (65536 * (((173 * pr[i - 1]) + 13849) / 65536))
for i in range(1, 24):
pr[i] = pr[i] / 32768

# demodulate ambe_fr with pr
k = 1
for j in range(22, -1, -1):
ambe_fr[1][j] = ((ambe_fr[1][j]) ^ pr[k])
k = k + 1
return ambe_fr # Pass it back since there is no pass by reference

def eccAmbe3600x2450Data(ambe_fr):
ambe = bitarray()

# just copy C0
for j in range(23, 11, -1):
ambe.append(ambe_fr[0][j])

for j in range(22, 10, -1):
ambe.append(ambe_fr[1][j])

# just copy C2
for j in range(10, -1, -1):
ambe.append(ambe_fr[2][j])

# just copy C3
for j in range(13, -1, -1):
ambe.append(ambe_fr[3][j])

return ambe

# Convert a 49 bit raw AMBE frame into a deinterleaved structure (ready for decode by AMBE3000)
def convert49BitAmbeTo72BitFrames(ambe_d):
index = 0
ambe_fr = [[None for x in range(24)] for y in range(4)]

#Place bits into the 4x24 frames. [bit0...bit23]
#fr0: [P e10 e9 e8 e7 e6 e5 e4 e3 e2 e1 e0 11 10 9 8 7 6 5 4 3 2 1 0]
#fr1: [e10 e9 e8 e7 e6 e5 e4 e3 e2 e1 e0 23 22 21 20 19 18 17 16 15 14 13 12 xx]
#fr2: [34 33 32 31 30 29 28 27 26 25 24 x x x x x x x x x x x x x]
#fr3: [48 47 46 45 44 43 42 41 40 39 38 37 36 35 x x x x x x x x x x]

# ecc and copy C0: 12bits + 11ecc + 1 parity
# First get the 12 bits that actually exist
# Then calculate the golay codeword
# And then add the parity bit to get the final 24 bit pattern

tmp = 0
for i in range(11, -1, -1): #grab the 12 MSB
tmp = (tmp << 1) | ambe_d[i]
tmp = golay2312(tmp) #Generate the 23 bit result
parityBit = parity(tmp)
tmp = tmp | (parityBit << 23) #And create a full 24 bit value
for i in range(23, -1, -1):
ambe_fr[0][i] = (tmp & 1)
tmp = tmp >> 1

# C1: 12 bits + 11ecc (no parity)
tmp = 0
for i in range(23,11, -1) : #grab the next 12 bits
tmp = (tmp << 1) | ambe_d[i]
tmp = golay2312(tmp) #Generate the 23 bit result
for j in range(22, -1, -1):
ambe_fr[1][j] = (tmp & 1)
tmp = tmp >> 1;

#C2: 11 bits (no ecc)
for j in range(10, -1, -1):
ambe_fr[2][j] = ambe_d[34 - j]

#C3: 14 bits (no ecc)
for j in range(13, -1, -1):
ambe_fr[3][j] = ambe_d[48 - j];

return ambe_fr

def interleave(ambe_fr):
bitIndex = 0
w = 0
x = 0
y = 0
z = 0
data = bytearray(9)
for i in range(36):
bit1 = ambe_fr[rW[w]][rX[x]] # bit 1
bit0 = ambe_fr[rY[y]][rZ[z]] # bit 0


data[bitIndex / 8] = ((data[bitIndex / 8] << 1) & 0xfe) | (1 if (bit1 == 1) else 0)
bitIndex += 1

data[bitIndex / 8] = ((data[bitIndex / 8] << 1) & 0xfe) | (1 if (bit0 == 1) else 0)
bitIndex += 1

w += 1
x += 1
y += 1
z += 1
return data

def deinterleave(data):

ambe_fr = [[None for x in range(24)] for y in range(4)]

bitIndex = 0
w = 0
x = 0
y = 0
z = 0
for i in range(36):
bit1 = 1 if data[bitIndex] else 0
bitIndex += 1

bit0 = 1 if data[bitIndex] else 0
bitIndex += 1

ambe_fr[rW[w]][rX[x]] = bit1
ambe_fr[rY[y]][rZ[z]] = bit0

w += 1
x += 1
y += 1
z += 1

return ambe_fr

def convert72BitTo49BitAMBE(ambe72):
ambe_fr = deinterleave(ambe72) # take 72 bit ambe and lay it out in C0-C3
ambe_fr = demodulateAmbe3600x2450(ambe_fr) # demodulate C1
ambe49 = eccAmbe3600x2450Data(ambe_fr) # pick out the 49 bits of raw ambe
return ambe49

def convert49BitTo72BitAMBE(ambe49):
ambe_fr = convert49BitAmbeTo72BitFrames(ambe49) # take raw ambe 49 + ecc and place it into C0-C3
ambe_fr = demodulateAmbe3600x2450(ambe_fr) # demodulate C1
ambe72 = interleave(ambe_fr) # Re-interleave it, returning 72 bits
return ambe72

if __name__ == '__main__':
for line in iter(sys.stdin.readline, b''):
line = line.rstrip()
parts = [line[:2*9], line[2*9:4*9], line[4*9:]]
for part in parts:
try:
print(ahex(convert72BitTo49BitAMBE(BitArray("0x" + part))))
except:
pass
sys.stdout.flush()
11 changes: 11 additions & 0 deletions src/od_tr_hack.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/python3

import sys
while True:
inp = sys.stdin.buffer.read(27)
if not inp:
# raise ValueError("input empty")
sys.exit()
if len(inp) != 27:
raise ValueError(f"incorrect len {len(inp)}")
print("".join(f"{b:02x}" for b in inp), flush=True)
34 changes: 34 additions & 0 deletions src/ruby-client.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
require 'mumble-ruby'
require 'json'

json_from_file = File.read('config.json')
config = JSON.parse(json_from_file)['mumble']

Mumble.configure do |conf|
# sample rate of sound (48 khz recommended)
conf.sample_rate = 48000

# bitrate of sound (32 kbit/s recommended)
conf.bitrate = 32000

# directory to store user's ssl certs
conf.ssl_cert_opts[:cert_dir] = File.expand_path("./certs/")
end

# Create client instance for your server
cli = Mumble::Client.new(config['server']) do |conf|
conf.username = config['username']
conf.password = config['password']
end

cli.connect()
while cli.channels.empty? do
sleep 1
puts "waiting until fully connected"
end
cli.join_channel(config['channel'])
cli.player.stream_named_pipe(config['fifo'])

while true do
sleep 5
end
Loading

2 comments on commit a8e5afb

@stefansaraev
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have something better. feel free to contact me on telegram ;)

@redfast00
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stefansaraev I don't have telegram anymore, but am curious. Would you mind sending me an email instead? My contact details are on https://redfast00.github.io/about/

Please sign in to comment.