Howto: Record controller input and convert to macro

Discuss everything to do with GIMX here
Post Reply
C64
Posts: 5
Joined: Mon Sep 02, 2019 4:06 pm

Howto: Record controller input and convert to macro

Post by C64 »

Update: there is an easier python script now, read my post below (#4)

I was looking for a way to capture all movements in part of a game and convert this capture to a macro so I could play back this 'recording' several times, using the Gimx adapter. Maybe this could help others :D
So this is what I did:

I used a DS4 controller, connected to a pc (Windows). And the GIMX adapter between my pc and my PS4.
Used the normal Dualshock4.xml and the GIMX adapter flashed with firmware for DS4.

I used the GIMX software (7.15) to record everything to a log file (Messages: log file)

Some lines of the log.txt file:
0 1565734741.205656, lstick x (-57), lstick y (-126), rstick x (-128), rstick y (33), l3 (255)
0 1565734741.215656, lstick x (-56), lstick y (-127), rstick x (29), rstick y (-10), l3 (255)
0 1565734741.225656, lstick x (-57), lstick y (-127), rstick x (1), rstick y (-5), l3 (255)
0 1565734741.245656, lstick x (-57), lstick y (-128), rstick x (1), rstick y (-5), l3 (255)
0 1565734741.255656, lstick x (-56), lstick y (-128), rstick x (1), rstick y (-5)

What I found out:
  • The time difference between two lines is 0.01 second (or 10 ms) (in my case, I think this can differ with other hardware, platforms, usb / bt
  • If no key was pressed / released or the position of the axis / stick(s) didn't change during 10 ms, there was no line in log.txt, for example 1565734741.235 is missing
  • When a button is (still) pressed, it is logged, for example L3 (255)
  • When a button is released, it isn't logged

I used this bash script to convert the log.txt to a sqlite file / csv file.
The log.txt needs to be in the same dir as the script.

How to use:
copy this code to a file (Linux / OSX), for example script.sh

set -x to make script executable with:

Code: Select all

chmod 770 script.sh

start script with:

Code: Select all

script.sh [replace with name of logfile]

for example

Code: Select all

script.sh log.txt

script.sh:

Code: Select all

#!/bin/sh

# check if dir exists
if [ ! -d ./out ]; then
	mkdir ./out
fi
if [ ! -d ./temp ]; then
        mkdir ./temp
fi

# cleanup 
rm -f ./temp/*
rm -f ./out/$1.db
rm -f ./out/$1.csv

# filter only 2nd part of log.txt
cat $1 | grep "^0 15" | sed 's/^0 //' > ./temp/temp

# max 9 fields, change if more
cat ./temp/temp | cut -d\, -f1,2 | grep , > ./temp/temp.$1.csv
cat ./temp/temp | cut -d\, -f1,3 | grep , >> ./temp/temp.$1.csv
cat ./temp/temp | cut -d\, -f1,4 | grep , >> ./temp/temp.$1.csv
cat ./temp/temp | cut -d\, -f5 | grep -v "^$" | wc -l
cat ./temp/temp | cut -d\, -f1,5 | grep , >> ./temp/temp.$1.csv
cat ./temp/temp | cut -d\, -f6 | grep -v "^$" | wc -l
cat ./temp/temp | cut -d\, -f1,6 | grep , >> ./temp/temp.$1.csv
cat ./temp/temp | cut -d\, -f7 | grep -v "^$" | wc -l
cat ./temp/temp | cut -d\, -f1,7 | grep , >> ./temp/temp.$1.csv
cat ./temp/temp | cut -d\, -f8 | grep -v "^$" | wc -l
cat ./temp/temp | cut -d\, -f1,8 | grep , >> ./temp/temp.$1.csv
cat ./temp/temp | cut -d\, -f9 | grep -v "^$" | wc -l
cat ./temp/temp | cut -d\, -f1,9 | grep , >> ./temp/temp.$1.csv
cat ./temp/temp | cut -d\, -f1  > ./out/$1.tijdstippen.csv

# split axis , buttons
cat ./temp/temp.$1.csv | grep lstick\ x > ./temp/$1.lstickx
cat ./temp/temp.$1.csv | grep lstick\ y > ./temp/$1.lsticky
cat ./temp/temp.$1.csv | grep rstick\ x > ./temp/$1.rstickx
cat ./temp/temp.$1.csv | grep rstick\ y > ./temp/$1.rsticky
cat ./temp/temp.$1.csv | grep l2 > ./temp/$1.l2
cat ./temp/temp.$1.csv | grep r2 > ./temp/$1.r2
cat ./temp/temp.$1.csv | grep -v lstick | grep -v rstick | grep -v l2 | grep -v r2 > ./temp/$1.overig

# create sqlite db
sqlite3 ./out/$1.db << EOF
create table data (id INTEGER PRIMARY KEY,tijd TEXT,ra0 INTEGER,ra1 INTEGER,ra2 INTEGER,ra3 INTEGER,ra4 INTEGER,ra5 INTEGER,ra6 INTEGER,ra7 INTEGER,aa0 INTEGER,aa1 INTEGER,aa2 INTEGER,aa3 INTEGER,aa4 INTEGER,aa5 INTEGER,aa6 INTEGER,aa7 INTEGER,aa8 INTEGER,aa9 INTEGER,aa10 INTEGER,aa11 INTEGER,aa12 INTEGER,aa13 INTEGER,aa14 INTEGER,aa15 INTEGER,aa16 INTEGER,aa17 INTEGER,aa18 INTEGER,aa19 INTEGER);
EOF

# insert all timevalues
sqlite3 ./out/$1.db << EOF
CREATE TABLE _csv_import (tijdtemp text);
.separator ","
.import ./out/$1.tijdstippen.csv _csv_import

INSERT INTO data (tijd) SELECT tijdtemp
    FROM _csv_import WHERE 1;
DROP TABLE _csv_import;
EOF


# fill db with values from log
rm -f ./temp/input.csv
cat ./temp/$1.lstickx | sed 's/\ //g' | sed 's/lstickx//' | sed 's/(//' | sed 's/)//' > ./temp/input.csv

# import csv 
sqlite3 ./out/$1.db << EOF
CREATE TABLE _csv_import (tijdtemp text, waarde INTEGER);
.separator ","
.import ./temp/input.csv _csv_import
EOF

# all rights for user / group
chmod 770 ./out/$1.db

# import lstickx values 
sqlite3 ./out/$1.db << EOF
CREATE TABLE TEST AS
    SELECT a.tijd AS tijd, _csv_import.waarde AS ra0,a.ra1,a.ra2,a.ra3,a.ra4,a.ra5,a.ra6,a.ra7,a.aa0,a.aa1,a.aa2,a.aa3,a.aa4,a.aa5,a.aa6,a.aa7,a.aa8,a.aa9,a.aa10,a.aa11,a.aa12,a.aa13,a.aa14,a.aa15,a.aa16,a.aa17,a.aa18,a.aa19
    FROM data a LEFT JOIN
         _csv_import
         ON a.tijd = _csv_import.tijdtemp;
DROP TABLE data;
create table data (id INTEGER PRIMARY KEY,tijd TEXT,ra0 INTEGER,ra1 INTEGER,ra2 INTEGER,ra3 INTEGER,ra4 INTEGER,ra5 INTEGER,ra6 INTEGER,ra7 INTEGER,aa0 INTEGER,aa1 INTEGER,aa2 INTEGER,aa3 INTEGER,aa4 INTEGER,aa5 INTEGER,aa6 INTEGER,aa7 INTEGER,aa8 INTEGER,aa9 INTEGER,aa10 INTEGER,aa11 INTEGER,aa12 INTEGER,aa13 INTEGER,aa14 INTEGER,aa15 INTEGER,aa16 INTEGER,aa17 INTEGER,aa18 INTEGER,aa19 INTEGER);
INSERT INTO data (tijd,ra0,ra1,ra2,ra3,ra4,ra5,ra6,ra7,aa0,aa1,aa2,aa3,aa4,aa5,aa6,aa7,aa8,aa9,aa10,aa11,aa12,aa13,aa14,aa15,aa16,aa17) SELECT tijd,ra0,ra1,ra2,ra3,ra4,ra5,ra6,ra7,aa0,aa1,aa2,aa3,aa4,aa5,aa6,aa7,aa8,aa9,aa10,aa11,aa12,aa13,aa14,aa15,aa16,aa17
    FROM TEST WHERE 1;
DROP TABLE TEST;
DROP TABLE _csv_import;
EOF

# same for lsticky, i am sure this can be done with less code :)
rm -f ./temp/input.csv
cat ./temp/$1.lsticky | sed 's/\ //g' | sed 's/lsticky//' | sed 's/(//' | sed 's/)//' > ./temp/input.csv

sqlite3 ./out/$1.db << EOF
CREATE TABLE _csv_import (tijdtemp text, waarde INTEGER);
.separator ","
.import ./temp/input.csv _csv_import
EOF

sqlite3 ./out/$1.db << EOF
CREATE TABLE TEST AS
    SELECT a.tijd AS tijd,a.ra0,_csv_import.waarde AS ra1,a.ra2,a.ra3,a.ra4,a.ra5,a.ra6,a.ra7,a.aa0,a.aa1,a.aa2,a.aa3,a.aa4,a.aa5,a.aa6,a.aa7,a.aa8,a.aa9,a.aa10,a.aa11,a.aa12,a.aa13,a.aa14,a.aa15,a.aa16,a.aa17,a.aa18,a.aa19
    FROM data a LEFT JOIN
         _csv_import
         ON a.tijd = _csv_import.tijdtemp;
DROP TABLE data;
create table data (id INTEGER PRIMARY KEY,tijd TEXT,ra0 INTEGER,ra1 INTEGER,ra2 INTEGER,ra3 INTEGER,ra4 INTEGER,ra5 INTEGER,ra6 INTEGER,ra7 INTEGER,aa0 INTEGER,aa1 INTEGER,aa2 INTEGER,aa3 INTEGER,aa4 INTEGER,aa5 INTEGER,aa6 INTEGER,aa7 INTEGER,aa8 INTEGER,aa9 INTEGER,aa10 INTEGER,aa11 INTEGER,aa12 INTEGER,aa13 INTEGER,aa14 INTEGER,aa15 INTEGER,aa16 INTEGER,aa17 INTEGER,aa18 INTEGER,aa19 INTEGER);
INSERT INTO data (tijd,ra0,ra1,ra2,ra3,ra4,ra5,ra6,ra7,aa0,aa1,aa2,aa3,aa4,aa5,aa6,aa7,aa8,aa9,aa10,aa11,aa12,aa13,aa14,aa15,aa16,aa17) SELECT tijd,ra0,ra1,ra2,ra3,ra4,ra5,ra6,ra7,aa0,aa1,aa2,aa3,aa4,aa5,aa6,aa7,aa8,aa9,aa10,aa11,aa12,aa13,aa14,aa15,aa16,aa17
    FROM TEST WHERE 1;
DROP TABLE TEST;
DROP TABLE _csv_import;
EOF

# rstickx
rm -f ./temp/input.csv
cat ./temp/$1.rstickx | sed 's/\ //g' | sed 's/rstickx//' | sed 's/(//' | sed 's/)//' > ./temp/input.csv

sqlite3 ./out/$1.db << EOF
CREATE TABLE _csv_import (tijdtemp text, waarde INTEGER);
.separator ","
.import ./temp/input.csv _csv_import
EOF

sqlite3 ./out/$1.db << EOF
CREATE TABLE TEST AS
    SELECT a.tijd AS tijd,a.ra0,a.ra1,_csv_import.waarde AS ra2,a.ra3,a.ra4,a.ra5,a.ra6,a.ra7,a.aa0,a.aa1,a.aa2,a.aa3,a.aa4,a.aa5,a.aa6,a.aa7,a.aa8,a.aa9,a.aa10,a.aa11,a.aa12,a.aa13,a.aa14,a.aa15,a.aa16,a.aa17,a.aa18,a.aa19
    FROM data a LEFT JOIN
         _csv_import
         ON a.tijd = _csv_import.tijdtemp;
DROP TABLE data;
create table data (id INTEGER PRIMARY KEY,tijd TEXT,ra0 INTEGER,ra1 INTEGER,ra2 INTEGER,ra3 INTEGER,ra4 INTEGER,ra5 INTEGER,ra6 INTEGER,ra7 INTEGER,aa0 INTEGER,aa1 INTEGER,aa2 INTEGER,aa3 INTEGER,aa4 INTEGER,aa5 INTEGER,aa6 INTEGER,aa7 INTEGER,aa8 INTEGER,aa9 INTEGER,aa10 INTEGER,aa11 INTEGER,aa12 INTEGER,aa13 INTEGER,aa14 INTEGER,aa15 INTEGER,aa16 INTEGER,aa17 INTEGER,aa18 INTEGER,aa19 INTEGER);
INSERT INTO data (tijd,ra0,ra1,ra2,ra3,ra4,ra5,ra6,ra7,aa0,aa1,aa2,aa3,aa4,aa5,aa6,aa7,aa8,aa9,aa10,aa11,aa12,aa13,aa14,aa15,aa16,aa17) SELECT tijd,ra0,ra1,ra2,ra3,ra4,ra5,ra6,ra7,aa0,aa1,aa2,aa3,aa4,aa5,aa6,aa7,aa8,aa9,aa10,aa11,aa12,aa13,aa14,aa15,aa16,aa17
    FROM TEST WHERE 1;
DROP TABLE TEST;
DROP TABLE _csv_import;
EOF

# rsticky
rm -f ./temp/input.csv
cat ./temp/$1.rsticky | sed 's/\ //g' | sed 's/rsticky//' | sed 's/(//' | sed 's/)//' > ./temp/input.csv

sqlite3 ./out/$1.db << EOF
CREATE TABLE _csv_import (tijdtemp text, waarde INTEGER);
.separator ","
.import ./temp/input.csv _csv_import
EOF

sqlite3 ./out/$1.db << EOF
CREATE TABLE TEST AS
    SELECT a.tijd AS tijd,a.ra0,a.ra1,a.ra2,_csv_import.waarde AS ra3,a.ra4,a.ra5,a.ra6,a.ra7,a.aa0,a.aa1,a.aa2,a.aa3,a.aa4,a.aa5,a.aa6,a.aa7,a.aa8,a.aa9,a.aa10,a.aa11,a.aa12,a.aa13,a.aa14,a.aa15,a.aa16,a.aa17,a.aa18,a.aa19
    FROM data a LEFT JOIN
         _csv_import
         ON a.tijd = _csv_import.tijdtemp;
DROP TABLE data;
create table data (id INTEGER PRIMARY KEY,tijd TEXT,ra0 INTEGER,ra1 INTEGER,ra2 INTEGER,ra3 INTEGER,ra4 INTEGER,ra5 INTEGER,ra6 INTEGER,ra7 INTEGER,aa0 INTEGER,aa1 INTEGER,aa2 INTEGER,aa3 INTEGER,aa4 INTEGER,aa5 INTEGER,aa6 INTEGER,aa7 INTEGER,aa8 INTEGER,aa9 INTEGER,aa10 INTEGER,aa11 INTEGER,aa12 INTEGER,aa13 INTEGER,aa14 INTEGER,aa15 INTEGER,aa16 INTEGER,aa17 INTEGER,aa18 INTEGER,aa19 INTEGER);
INSERT INTO data (tijd,ra0,ra1,ra2,ra3,ra4,ra5,ra6,ra7,aa0,aa1,aa2,aa3,aa4,aa5,aa6,aa7,aa8,aa9,aa10,aa11,aa12,aa13,aa14,aa15,aa16,aa17) SELECT tijd,ra0,ra1,ra2,ra3,ra4,ra5,ra6,ra7,aa0,aa1,aa2,aa3,aa4,aa5,aa6,aa7,aa8,aa9,aa10,aa11,aa12,aa13,aa14,aa15,aa16,aa17
    FROM TEST WHERE 1;
DROP TABLE TEST;
DROP TABLE _csv_import;
EOF

# L2
rm -f ./temp/input.csv
cat ./temp/$1.l2 | sed 's/\ //g' | sed 's/l2//' | sed 's/(//' | sed 's/)//' > ./temp/input.csv

sqlite3 ./out/$1.db << EOF
CREATE TABLE _csv_import (tijdtemp text, waarde INTEGER);
.separator ","
.import ./temp/input.csv _csv_import
EOF

sqlite3 ./out/$1.db << EOF
CREATE TABLE TEST AS
    SELECT a.tijd AS tijd,a.ra0,a.ra1,a.ra2,ra3,a.ra4,a.ra5,a.ra6,a.ra7,a.aa0,a.aa1,a.aa2,a.aa3,a.aa4,a.aa5,a.aa6,a.aa7,a.aa8,a.aa9,a.aa10,a.aa11,a.aa12,_csv_import.waarde AS aa13,a.aa14,a.aa15,a.aa16,a.aa17,a.aa18,a.aa19
    FROM data a LEFT JOIN
         _csv_import
         ON a.tijd = _csv_import.tijdtemp;
DROP TABLE data;
create table data (id INTEGER PRIMARY KEY,tijd TEXT,ra0 INTEGER,ra1 INTEGER,ra2 INTEGER,ra3 INTEGER,ra4 INTEGER,ra5 INTEGER,ra6 INTEGER,ra7 INTEGER,aa0 INTEGER,aa1 INTEGER,aa2 INTEGER,aa3 INTEGER,aa4 INTEGER,aa5 INTEGER,aa6 INTEGER,aa7 INTEGER,aa8 INTEGER,aa9 INTEGER,aa10 INTEGER,aa11 INTEGER,aa12 INTEGER,aa13 INTEGER,aa14 INTEGER,aa15 INTEGER,aa16 INTEGER,aa17 INTEGER,aa18 INTEGER,aa19 INTEGER);
INSERT INTO data (tijd,ra0,ra1,ra2,ra3,ra4,ra5,ra6,ra7,aa0,aa1,aa2,aa3,aa4,aa5,aa6,aa7,aa8,aa9,aa10,aa11,aa12,aa13,aa14,aa15,aa16,aa17) SELECT tijd,ra0,ra1,ra2,ra3,ra4,ra5,ra6,ra7,aa0,aa1,aa2,aa3,aa4,aa5,aa6,aa7,aa8,aa9,aa10,aa11,aa12,aa13,aa14,aa15,aa16,aa17
    FROM TEST WHERE 1;
DROP TABLE TEST;
DROP TABLE _csv_import;
EOF

# R2
rm -f ./temp/input.csv
cat ./temp/$1.r2 | sed 's/\ //g' | sed 's/r2//' | sed 's/(//' | sed 's/)//' > ./temp/input.csv

sqlite3 ./out/$1.db << EOF
CREATE TABLE _csv_import (tijdtemp text, waarde INTEGER);
.separator ","
.import ./temp/input.csv _csv_import
EOF

sqlite3 ./out/$1.db << EOF
CREATE TABLE TEST AS
    SELECT a.tijd AS tijd,a.ra0,a.ra1,a.ra2,ra3,a.ra4,a.ra5,a.ra6,a.ra7,a.aa0,a.aa1,a.aa2,a.aa3,a.aa4,a.aa5,a.aa6,a.aa7,a.aa8,a.aa9,a.aa10,a.aa11,a.aa12,a.aa13,_csv_import.waarde AS aa14,a.aa15,a.aa16,a.aa17,a.aa18,a.aa19
    FROM data a LEFT JOIN
         _csv_import
         ON a.tijd = _csv_import.tijdtemp;
DROP TABLE data;
create table data (id INTEGER PRIMARY KEY,tijd TEXT,ra0 INTEGER,ra1 INTEGER,ra2 INTEGER,ra3 INTEGER,ra4 INTEGER,ra5 INTEGER,ra6 INTEGER,ra7 INTEGER,aa0 INTEGER,aa1 INTEGER,aa2 INTEGER,aa3 INTEGER,aa4 INTEGER,aa5 INTEGER,aa6 INTEGER,aa7 INTEGER,aa8 INTEGER,aa9 INTEGER,aa10 INTEGER,aa11 INTEGER,aa12 INTEGER,aa13 INTEGER,aa14 INTEGER,aa15 INTEGER,aa16 INTEGER,aa17 INTEGER,aa18 INTEGER,aa19 INTEGER);
INSERT INTO data (tijd,ra0,ra1,ra2,ra3,ra4,ra5,ra6,ra7,aa0,aa1,aa2,aa3,aa4,aa5,aa6,aa7,aa8,aa9,aa10,aa11,aa12,aa13,aa14,aa15,aa16,aa17) SELECT tijd,ra0,ra1,ra2,ra3,ra4,ra5,ra6,ra7,aa0,aa1,aa2,aa3,aa4,aa5,aa6,aa7,aa8,aa9,aa10,aa11,aa12,aa13,aa14,aa15,aa16,aa17
    FROM TEST WHERE 1;
DROP TABLE TEST;
DROP TABLE _csv_import;
EOF


# allbuttons
knopnr=0
for knop in share options PS up right down left triangle circle cross square l1 r1 l3 r3
do
	if [ $knopnr -eq 13 ]
	then
		knopnr=$[$knopnr+2]
	fi
	rm -f ./temp/input.csv
	cat ./temp/$1.overig | sed 's/\ //g' | grep $knop | sed 's/'"$knop"'//' | sed 's/(//' | sed 's/)//' > ./temp/input.csv

	for regel in `cat ./temp/input.csv`
	do
	       	tijd=`echo $regel | cut -d\, -f1`
       		waarde=`echo $regel | cut -d\, -f2`
       		sqlite3 ./out/$1.db "update data set aa$knopnr='$waarde' where tijd='$tijd';"
       		tijd=""
       		waarde=""
	done
	
	knopnr=$[$knopnr+1]
done

rm -f ./temp/*

rm -f ./out/$1.tijdstippen.csv

# write all to csv
sqlite3 ./out/$1.db << EOF
.head on
.mode csv
.output ./out/$1.csv
select tijd, 
ra0 as lstickx,
ra1 as lsticky,
ra2 as rstickx,
ra3 as rsticky,
aa0 as share,
aa1 as options,
aa2 as PS,
aa3 as up,
aa4 as right,
aa5 as down,
aa6 as left,
aa7 as triangle,
aa8 as circle,
aa9 as cross,
aa10 as square,
aa11 as l1,
aa12 as r1,
aa13 as l2,
aa14 as r2,
aa15 as l3,
aa16 as r3 
from data;
.quit
EOF

After this I opened the csv file (in the dir named 'out') with Excel.
For the axis values (lstick x/y, rstick x/y, L2, R2) I "filled down" values when they were empty (copy value from previous line).

Used the following formulas to convert -255 .. 255 and -128 .. 128 (from log.txt) to -32768 .. 32767 (in macro)

For AXIS lstickx / lstick y / rstick x / rstick y:

Code: Select all

min([ref to cell with value from log.txt]*256;32767)

For L2 / R2:

Code: Select all

MIN([ref to cell with value from log.txt]×128+127;32767)

Now combine JAXIS [id] with new calculated values using (ID=0 for lstick x):

Code: Select all

="JAXIS 0 " & [ref to new calculated value]

When you use Dualshock4.xml
  • lstickx: JAXIS 0
  • lsticky: JAXIS 1
  • rstickx: JAXIS 2
  • rsticy: JAXIS 3
  • L2: JAXIS 4
  • R2: JAXIS 5



I used VBA for Excel to fill all values, formulas and convert all code to one line per command. I will post the code for VBA later.

For Buttons, at first line with 255 use:

Code: Select all

JBUTTONDOWN [id]
(replace [id] with corresponding id for button in Dualshock4.xml

and for the line right after where 255 ends :

Code: Select all

JBUTTONUP [id]

Finally for every line use:

Code: Select all

DELAY 10

When you want to test parts of your macro always end your macro with:

Code: Select all

DELAY 10
JAXIS 0 0
JAXIS 1 0
JAXIS 2 0
JAXIS 3 0
JAXIS 4 0
JAXIS 5 0

So the few lines given earlier as an example from log.txt will be converted to a macro, like this:

Code: Select all

JAXIS 0 -14592
JAXIS 1 -32256
JAXIS 2 -32768
JAXIS 3 8448
JBUTTONDOWN 7
DELAY 10
JAXIS 0 -14336
JAXIS 1 -32512
JAXIS 2 7424
JAXIS 3 -2560
DELAY 10
JAXIS 0 -14592
JAXIS 1 -32512
JAXIS 2 256
JAXIS 3 -1280
DELAY 10
# copy from previous line because nothing changed (xxx.235)
# maybe DELAY 20 will work too
# not tested
JAXIS 0 -14592
JAXIS 1 -32512
JAXIS 2 256
JAXIS 3 -1280
DELAY 10
JAXIS 0 -14592
JAXIS 1 -32768
JAXIS 2 256
JAXIS 3 -1280
DELAY 10
JAXIS 0 -14336
JAXIS 1 -32768
JAXIS 2 256
JAXIS 3 -1280
JBUTTONUP 7
Last edited by C64 on Sun Jun 13, 2021 1:25 pm, edited 2 times in total.
User avatar
GoDlike
Posts: 1319
Joined: Thu Apr 28, 2016 12:47 pm
Location: Poland

Re: Howto: Record controller input and convert to macro

Post by GoDlike »

Great work mate. We really appreciate that.
My hardware: PS3 Slim CFW 4.80 | PS4 Pro 500 Million LE | PS5 | Xbox Series X
Steam: Godlike_RU | PSN: GoDlike_RU | XBL: GoDlike
C64
Posts: 5
Joined: Mon Sep 02, 2019 4:06 pm

Re: Howto: Record controller input and convert to macro

Post by C64 »

Thnx !

I made some changes to the script:
  • cleaned up and re-used some code
  • started to get all code within script.sh so Excel and VBA are not needed anymore
  • the values within the database / csv are already converted for the macro, for example [-32767..32768] instead of [-128..127]
  • the script is quicker

Script.sh [v1.01]:

Code: Select all

#!/bin/sh

# v1.01
# cleaned up / re-used code
# values are already converted for macro
# script is a lot quicker

# check if dir exists
if [ ! -d ./out ]; then
	mkdir ./out
fi
if [ ! -d ./temp ]; then
        mkdir ./temp
fi

# cleanup
rm -f ./temp/*
rm -f ./out/$1.db
rm -f ./out/$1.csv

# filter and only use 2nd part of log.txt
cat $1 | grep "^0 15" | sed 's/^0 //' > ./temp/temp

# transpose
counter=2
echo
echo
echo "Check last line, should be: 0, otherwise change code to read more fields"

# max 12 fields, change if needed more
while [ $counter -lt 12 ]
do
	cat ./temp/temp | cut -d\, -f1,$counter | grep , >> ./temp/temp.$1.csv
	cat ./temp/temp | cut -d\, -f$counter | grep -v "^$" | wc -l	
	counter=$[$counter+1]
done

# all timevalues to csv
cat ./temp/temp | cut -d\, -f1  > ./out/$1.tijdstippen.csv

# split axis , buttons
cat ./temp/temp.$1.csv | grep lstick\ x > ./temp/$1.lstickx
cat ./temp/temp.$1.csv | grep lstick\ y > ./temp/$1.lsticky
cat ./temp/temp.$1.csv | grep rstick\ x > ./temp/$1.rstickx
cat ./temp/temp.$1.csv | grep rstick\ y > ./temp/$1.rsticky
cat ./temp/temp.$1.csv | grep l2 > ./temp/$1.l2
cat ./temp/temp.$1.csv | grep r2 > ./temp/$1.r2
cat ./temp/temp.$1.csv | grep -v lstick | grep -v rstick | grep -v l2 | grep -v r2 > ./temp/$1.overig

# create sqlite db
sqlite3 ./out/$1.db << EOF
create table data (id INTEGER PRIMARY KEY,tijd TEXT,ra0 INTEGER,ra1 INTEGER,ra2 INTEGER,ra3 INTEGER,ra4 INTEGER,ra5 INTEGER,ra6 INTEGER,ra7 INTEGER,aa0 INTEGER,aa1 INTEGER,aa2 INTEGER,aa3 INTEGER,aa4 INTEGER,aa5 INTEGER,aa6 INTEGER,aa7 INTEGER,aa8 INTEGER,aa9 INTEGER,aa10 INTEGER,aa11 INTEGER,aa12 INTEGER,aa13 INTEGER,aa14 INTEGER,aa15 INTEGER,aa16 INTEGER,aa17 INTEGER,aa18 INTEGER,aa19 INTEGER);
EOF

# insert all timevalues
sqlite3 ./out/$1.db << EOF
CREATE TABLE _csv_import (tijdtemp text);
.separator ","
.import ./out/$1.tijdstippen.csv _csv_import
INSERT INTO data (tijd) SELECT tijdtemp
    FROM _csv_import WHERE 1;
DROP TABLE _csv_import;
EOF

# change rights
chmod 770 ./out/$1.db

# add values [lr]stick[xy] to db
# and convert [-128..127] to [-32768..32767]

knopnr=0
for knop in lstickx lsticky rstickx rsticky
do

	rm -f ./temp/input.csv
	cat ./temp/$1.$knop | sed 's/\ //g' | sed 's/'"$knop"'//' | sed 's/(//' | sed 's/)//' > ./temp/input.csv

sqlite3 ./out/$1.db << EOF
CREATE TABLE _csv_import (tijdtemp text, waarde INTEGER);
.separator ","
.import ./temp/input.csv _csv_import
update data set ra$knopnr = (256*(select waarde from _csv_import where data.tijd=_csv_import.tijdtemp));
DROP table _csv_import
EOF

knopnr=$[$knopnr+1]

done

# add values l2 and r2 to db
# and convert [0..255] to [0..32767]
 
knopnr=13
for knop in l2 r2
do

        rm -f ./temp/input.csv
        cat ./temp/$1.$knop | sed 's/\ //g' | sed 's/'"$knop"'//' | sed 's/(//' | sed 's/)//' > ./temp/input.csv

sqlite3 ./out/$1.db << EOF
CREATE TABLE _csv_import (tijdtemp text, waarde INTEGER);
.separator ","
.import ./temp/input.csv _csv_import
update data set aa$knopnr = (128.5*(select waarde from _csv_import where data.tijd=_csv_import.tijdtemp));
DROP table _csv_import
EOF

knopnr=$[$knopnr+1]

done

# set max to 32767 for [lr]stick[xy]
sqlite3 ./out/$1.db << EOF
update data set ra0=32767 where ra0=32768;
update data set ra1=32767 where ra1=32768;
update data set ra2=32767 where ra2=32768;
update data set ra3=32767 where ra3=32768;
EOF

# remove decimals
sqlite3 ./out/$1.db << EOF
update data set aa13=cast(aa13 as int) where aa13 like '%.5';
update data set aa14=cast(aa14 as int) where aa14 like '%.5';
EOF


# allbuttons
knopnr=0
for knop in share options PS up right down left triangle circle cross square l1 r1 l3 r3
do
	if [ $knopnr -eq 13 ]
	then
		knopnr=$[$knopnr+2]
	fi
	rm -f ./temp/input.csv
	cat ./temp/$1.overig | sed 's/\ //g' | grep $knop | sed 's/'"$knop"'//' | sed 's/(//' | sed 's/)//' > ./temp/input.csv

sqlite3 ./out/$1.db << EOF
CREATE TABLE _csv_import (tijdtemp text, waarde INTEGER);
.separator ","
.import ./temp/input.csv _csv_import
update data set aa$knopnr = (select waarde from _csv_import where data.tijd=_csv_import.tijdtemp);
DROP TABLE _csv_import;
EOF

knopnr=$[$knopnr+1]

done

rm -f ./temp/*

rm -f ./out/$1.tijdstippen.csv

# write all to csv
sqlite3 ./out/$1.db << EOF
.head on
.mode csv
.output ./out/$1.csv
select tijd, 
ra0 as lstickx,
ra1 as lsticky,
ra2 as rstickx,
ra3 as rsticky,
aa0 as share,
aa1 as options,
aa2 as PS,
aa3 as up,
aa4 as right,
aa5 as down,
aa6 as left,
aa7 as triangle,
aa8 as circle,
aa9 as cross,
aa10 as square,
aa11 as l1,
aa12 as r1,
aa13 as l2,
aa14 as r2,
aa15 as l3,
aa16 as r3 
from data;
.quit
EOF

To do: (in the meantime can be done using Excel)
  • add lines where time difference >= 20 ms
  • 'fill-down values for lines added
  • mark every first line right after button=255 for BUTTONUP
  • write all to txt with "JAXIS [id] [value] and BUTTON[UP|DOWN] [id]

Question:

Will the result be the same ?

Example 1:

Code: Select all

JAXIS 0 32767
DELAY 10
JAXIS 0 32767
DELAY 10
JAXIS 0 0
Example 2:

Code: Select all

JAXIS 0 32767
DELAY 20
JAXIS 0 0
Or is Example 1 better when for some reason a line is skipped by gimx adapter / software sometimes ?
C64
Posts: 5
Joined: Mon Sep 02, 2019 4:06 pm

Re: Howto: Record controller input and convert to macro

Post by C64 »

My son converted the scripts to python. The python script is ready to use. You don't need any other tools.

How to use:
copy this code to a file named log2macro.py

Code: Select all

import csv, sys, os

tempFol = "./temp"
outFol = "./out"
infile = sys.argv[1]
name = sys.argv[2] + "_macro.txt"
filePath = outFol + "/" + name

# values
MAX_AXIS_VALUE_16BITS = 65535
MAX_AXIS_VALUE_8BITS = 255
triggervalue = (MAX_AXIS_VALUE_16BITS / 2) / MAX_AXIS_VALUE_8BITS
axisvalue = (MAX_AXIS_VALUE_16BITS / 2) / (MAX_AXIS_VALUE_8BITS / 2)

# Check if dir exists, if not create it
if not os.path.exists(tempFol):
    os.makedirs(tempFol)
if not os.path.exists(outFol):
    os.makedirs(outFol)

# Check if name is correct, if not abort!
if name == None:
    sys.exit("ERROR: Please provide a name for the output file as an argument. The extension is not necessary, Syntax: python3 log2macro.py <input file> <output name>");
# Check if file exists, if it does abort!
if os.path.isfile(filePath):
    sys.exit("ERROR: File with name: " + name + " already exists in the output folder. Please provide a different name or remove the file manually")

# Create a output file if necessary and write the header to it
with open(filePath, "w+") as f, open(infile, "r") as input:
    button = ["cross", "circle", "square", "triangle", "share", "PS", "options", "l3", "r3", "l1", "r1", "up", "down", "left", "right", "touchpad"]
    axis = ["lstickx", "lsticky", "rstickx", "rsticky", "l2", "r2"]
    lines = input.readlines()
    input.close()

    lasttime = 0
    pressed = []
    currpressed = []

    # Get all the commands and write them to a new array
    for line in lines:
        if str.startswith(line, "0 "):
            # removing "0 " and making splitting the string into an array
            cutline = line[2:].split(",")
            # add the delay, only when lasttime != 0 (so not at start)
            if not (lasttime == 0):
                # f.write("DELAY " + str(int((float(cutline[0]) - lasttime) * 1000) + 1) + "\n") # OLD NO ROUDING
                f.write("DELAY " + str(round(int((float(cutline[0]) - lasttime) * 1000) + 1, -1)) + "\n")
            lasttime = float(cutline[0])
            # remember we use correct indexing here, instead of the +1 of cut
            # if we want to have until the 12th field, we need to go until 11
            for curr in cutline:
                # check if curr is not the time
                if not len(curr.split()) == 1:

                    # change the buttonname to the one in our array
                    buttonname = ""
                    if(len(curr.split()) == 3):
                        buttonname = curr.split()[0] + curr.split()[1]
                    else:
                        buttonname = curr.split()[0]

                    value = int(curr.split()[-1].replace("(", "").replace(")", ""))
                    outcom = ""
                    # check if it is a button or a axis
                    if buttonname in button:
                        # only down presses are registred. We also add it to a list,
                        # later we check if a button is still pressed or should be released
                        if not buttonname in pressed:
                            index = button.index(buttonname)
                            outcom = "JBUTTONDOWN " + str(index) + "\n"
                            print(buttonname + ": " + str(index))
                        currpressed.append(buttonname)

                    elif buttonname in axis:
                        index = axis.index(buttonname)
                        outcom = "JAXIS " + str(index) + " "
                        currpressed.append(buttonname)

                        # calculate the new value add it to outcom
                        if buttonname == "l2" or buttonname == "r2":
                            outcom += str(max(min(int(value * triggervalue), 32767), 0)) + "\n"
                        else:
                            outcom += str(max(min(int(value * axisvalue), 32767), -32768)) + "\n"

                    # write outcom to the outputfile
                    f.write(outcom)

        # check if buttons are released
       	for butt in pressed:
            if butt not in currpressed:
                if butt in button:
                    f.write("JBUTTONUP " + str(button.index(butt)) + "\n")
                elif butt in axis:
                    f.write("JAXIS " + str(axis.index(butt)) + " 0" + "\n")

        pressed = currpressed.copy()
        currpressed = [] 
    f.close()
I used gimx 8.00 to capture my DS4 input to a log.txt file.
Make sure your log.txt and log2macro.py are in the same folder.

Create a macro with:

Code: Select all

python3.9 log2macro.py log.txt runlevel1
As you can see I used python version 3.9.
I think any python 3.x will do but I didn't test.

Open the runlevel1_macro.txt file you just created, in the subfolder named 'out' and add a line to the top of the file like:

Code: Select all

MACRO KEYDOWN F2
Remove any unnecessary lines from the top of the macro from the start of your recording (for example the PS button to activate your DS4 and Cross button to close the 'no controller found' message), for example:

Code: Select all

JBUTTONDOWN 5
...
JBUTTONUP 5
JBUTTONDOWN 0
...
JBUTTONUP 0
Save the macro file, add a new line to configs.txt and have fun !

To do:
I did some testing and noticed some minor differences in the begin / end of pressing a trigger (L2 / R2). Not sure if this has to do with timing or conversion or something else.
Post Reply