Skip to content

Commit

Permalink
add simple input remapping support (no user interface yet)
Browse files Browse the repository at this point in the history
This reads %APPDATA%\DCS-BIOS\Config\inputmap.txt

Each line is either three or five comma-separated values:

AIRCRAFT,FROM,TO
remaps FROM to TO when AIRCRAFT is active in the simulator.

AIRCRAFT,FROM_CMD,FROM_ARG,TO_CMD,TO_ARG
remaps FROM_CMD with FROM_ARG to TO_CMD with TO_ARG when AIRCRAFT
is active in the simulator.

inputmap.txt is read on startup; to pick up changes, restart dcs-bios-hub.
  • Loading branch information
jboecker committed Oct 21, 2019
1 parent 9433948 commit b47b551
Show file tree
Hide file tree
Showing 5 changed files with 257 additions and 2 deletions.
4 changes: 4 additions & 0 deletions src/hub-backend/configstore/configstore.go
Expand Up @@ -59,3 +59,7 @@ func Store(filename string, data interface{}) error {
file.Close()
return nil
}

func OpenFile(filename string) (*os.File, error) {
return os.Open(getFilePath(filename))
}
29 changes: 27 additions & 2 deletions src/hub-backend/dcs-bios-hub.go
Expand Up @@ -7,12 +7,15 @@ import (
"net/http"
_ "net/http/pprof"
"os"
"strings"

"dcs-bios.a10c.de/dcs-bios-hub/configstore"
"dcs-bios.a10c.de/dcs-bios-hub/controlreference"
"dcs-bios.a10c.de/dcs-bios-hub/dcsconnection"
"dcs-bios.a10c.de/dcs-bios-hub/dcssetup"
"dcs-bios.a10c.de/dcs-bios-hub/exportdataparser"
"dcs-bios.a10c.de/dcs-bios-hub/gui"
"dcs-bios.a10c.de/dcs-bios-hub/inputmapping"
"dcs-bios.a10c.de/dcs-bios-hub/jsonapi"
"dcs-bios.a10c.de/dcs-bios-hub/livedataapi"
"dcs-bios.a10c.de/dcs-bios-hub/luaconsole"
Expand Down Expand Up @@ -105,19 +108,41 @@ func startServices() {
dcssetup.RegisterApi(jsonAPI)
dcssetup.GetInstalledModulesList()

inputmap := &inputmapping.InputRemapper{}
inputmap.LoadFromConfigStore()

exportDataParser := &exportdataparser.ExportDataParser{}
currentUnitType := "NONE"
exportDataParser.SubscribeStringBuffer(0, 16, func(nameBytes []byte) {
name := strings.Trim(string(nameBytes), " ")
if currentUnitType != name {
currentUnitType = name
statusapi.WithStatusInfoDo(func(status *statusapi.StatusInfo) {
status.UnitType = name
})
inputmap.SetActiveAircraft(name)
}
})

// transmit data between DCS and the serial ports
go func() {
for {
select {
case icstr := <-lda.InputCommands:
cmd := append(icstr, byte('\n'))
cmdStr := inputmap.Remap(string(icstr))
cmd := []byte(cmdStr + "\n")
dcsConn.TrySend(cmd)

case ic := <-portManager.InputCommands:
cmd := append(ic.Command, byte('\n'))
cmdStr := inputmap.Remap(string(ic.Command))
cmd := []byte(cmdStr + "\n")

dcsConn.TrySend(cmd)

case data := <-dcsConn.ExportData:
for _, b := range data {
exportDataParser.ProcessByte(b)
}
portManager.Write(data)
lda.WriteExportData(data)

Expand Down
138 changes: 138 additions & 0 deletions src/hub-backend/exportdataparser/exportdataparser.go
@@ -0,0 +1,138 @@
package exportdataparser

import (
"bytes"
"sync"
)

type parserState int

const (
StateWaitForSync = parserState(0)
StateAddressLow = parserState(1)
StateAddressHigh = parserState(2)
StateCountLow = parserState(3)
StateCountHigh = parserState(4)
StateDataLow = parserState(5)
StateDataHigh = parserState(6)
)

type stringBuffer struct {
address int
length int
data []byte
callback func([]byte)
}

type twoByteBuffer [2]byte

func (buf *twoByteBuffer) AsUint16() uint16 {
return uint16(buf[1])<<8 | uint16(buf[0])
}
func (buf *twoByteBuffer) SetUint16(n uint16) {
buf[0] = uint8(n & 0x00FF)
buf[1] = uint8((n & 0xFF00) >> 8)
}

type ExportDataParser struct {
state parserState
syncByteCount int
addressBuffer twoByteBuffer
countBuffer twoByteBuffer
dataBuffer twoByteBuffer
totalBuffer [65536]byte
stringBuffers []stringBuffer
stringBufferLock sync.Mutex
}

func (ep *ExportDataParser) SubscribeStringBuffer(address int, length int, callback func([]byte)) {
sb := stringBuffer{
address: address,
length: length,
data: make([]byte, length),
callback: callback,
}
ep.stringBufferLock.Lock()
ep.stringBuffers = append(ep.stringBuffers, sb)
ep.stringBufferLock.Unlock()
}

func (ep *ExportDataParser) ProcessByte(b uint8) {
switch ep.state {
case StateWaitForSync:

case StateAddressLow:
ep.addressBuffer[0] = b
ep.state = StateAddressHigh

case StateAddressHigh:
ep.addressBuffer[1] = b
if ep.addressBuffer.AsUint16() != 0x555 {
ep.state = StateCountLow
} else {
ep.state = StateWaitForSync
}

case StateCountLow:
ep.countBuffer[0] = b
ep.state = StateCountHigh

case StateCountHigh:
ep.countBuffer[1] = b
ep.state = StateDataLow

case StateDataLow:
ep.dataBuffer[0] = b
ep.state = StateDataHigh
ep.countBuffer.SetUint16(ep.countBuffer.AsUint16() - 1)
ep.state = StateDataHigh

case StateDataHigh:
ep.dataBuffer[1] = b
ep.countBuffer.SetUint16(ep.countBuffer.AsUint16() - 1)
ep.totalBuffer[ep.addressBuffer.AsUint16()] = ep.dataBuffer[0]
ep.totalBuffer[ep.addressBuffer.AsUint16()+1] = ep.dataBuffer[1]

if ep.addressBuffer.AsUint16() == 0xfffe {
// end of update
ep.notify()
}

ep.addressBuffer.SetUint16(ep.addressBuffer.AsUint16() + 2)

if ep.countBuffer.AsUint16() == 0 {
ep.state = StateAddressLow
} else {
ep.state = StateDataLow
}

}

if b == 0x55 {
ep.syncByteCount++
} else {
ep.syncByteCount = 0
}

if ep.syncByteCount == 4 {
ep.state = StateAddressLow
ep.syncByteCount = 0
}
}

func (ep *ExportDataParser) notify() {
ep.stringBufferLock.Lock()
for _, sb := range ep.stringBuffers {
for i := range sb.data {
sb.data[i] = ep.totalBuffer[sb.address+i]
}

nullTerminatorPos := bytes.IndexByte(sb.data, 0)
if nullTerminatorPos == -1 {
nullTerminatorPos = 0
}

sb.callback(sb.data[:nullTerminatorPos])
}
ep.stringBufferLock.Unlock()
}
87 changes: 87 additions & 0 deletions src/hub-backend/inputmapping/inputmapping.go
@@ -0,0 +1,87 @@
// Package inputmapping manages a translation table
package inputmapping

import (
"bufio"
"fmt"
"strings"

"dcs-bios.a10c.de/dcs-bios-hub/configstore"
)

type CommandMatch struct {
CommandMatch string
ArgumentMatch string
CommandReplacement string
ArgumentReplacement string
}

type InputMap map[string][]CommandMatch

type InputRemapper struct {
unittypeMappings map[string]InputMap
activeUnittype string
}

func (ir *InputRemapper) SetActiveAircraft(name string) {
ir.activeUnittype = name
}

func (ir *InputRemapper) LoadFromConfigStore() {
file, err := configstore.OpenFile("inputmap.txt")
if err != nil {
fmt.Printf("could not load inputmap.txt: %v\n", err.Error())
return
}
defer file.Close()

ir.unittypeMappings = make(map[string]InputMap)

scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
parts := strings.Split(line, ",")
cm := &CommandMatch{}
unittype := parts[0]
if len(parts) == 3 {
cm.CommandMatch = parts[1]
cm.CommandReplacement = parts[2]
} else if len(parts) == 5 {
cm.CommandMatch = parts[1]
cm.ArgumentMatch = parts[2]
cm.CommandReplacement = parts[3]
cm.ArgumentReplacement = parts[4]
} else {
continue
}

if _, ok := ir.unittypeMappings[unittype]; !ok {
ir.unittypeMappings[unittype] = make(InputMap)
}
ir.unittypeMappings[unittype][cm.CommandMatch] = append(ir.unittypeMappings[unittype][cm.CommandMatch], *cm)
}
}

func (ir *InputRemapper) Remap(line string) string {
mapping, ok := ir.unittypeMappings[ir.activeUnittype]
if !ok {
return line
}
parts := strings.Split(line, " ")
if len(parts) != 2 {
return line
}
msg, arg := parts[0], parts[1]

for _, cmdMatch := range mapping[msg] {
if cmdMatch.ArgumentMatch == arg || cmdMatch.ArgumentMatch == "" {
argRep := arg
if cmdMatch.ArgumentReplacement != "" {
argRep = cmdMatch.ArgumentReplacement
}
// found replacement
return cmdMatch.CommandReplacement + " " + argRep
}
}
return line
}
1 change: 1 addition & 0 deletions src/hub-backend/statusapi/statusapi.go
Expand Up @@ -16,6 +16,7 @@ type StatusInfo struct {
IsLuaConsoleConnected bool `json:"isLuaConsoleConnected"`
IsLuaConsoleEnabled bool `json:"isLuaConsoleEnabled"`
IsExternalNetworkAccessEnabled bool `json:"isExternalNetworkAccessEnabled"`
UnitType string `json:"unittype"`
}

var currentStatus StatusInfo
Expand Down

0 comments on commit b47b551

Please sign in to comment.