This is documentation for the current web API used on products based on FX-Framework.
It's a collection of endpoints accumulated over the years in the Web-UI. We're aware this isn't a web API in the modern sense.
Eventually there will be a proper API (version 1) in the future.
This document describes the endpoints available on the Morph 4K. Some (as the websocket interface) might not be available on G.E.M. or Digital products!
Work in progress: Especially the URI based interfaces aren't completely documented yet.
For easy debugging, we suggest using a command line websocket client like websocat
(in conjunction with readline wrapper rlwrap
)
Connect to ws://morph4k.local/ws/api
with websocket protocol morph4k
rlwrap websocat --protocol morph4k ws://morph4k.local/ws/api --linemode-strip-newlines
Send a single start_sdcard
command.
This will make sure, the sdcard command handling task is running on the ESP.
You will notice a 1
(in case the SD card is mounted) or a 0
is sent by the server side every second.
This can be used for both an alive check and informs about the current sdcard state (mounted/not mounted)
At the end of a session, you should send stop_sdcard
command, so the ESP can free up memory, if the interface isn't needed.
sdcard=<JSON>
Specifically:
sdcard={"a":"<ACTION>","v":["<ARG1>","<ARG2>",....]}
Example:
Requesting directory listing for /sdcard/presets
sdcard={"a":"list","v":["/sdcard/presets"]}
Most commands reply with the directory listing reply
{"e":[<ELEMENT1>,<ELEMENT2>,...],"r":"<DIRECTORY_CONTEXT>"}
Each is element is either a file:
{"n":"<NAME>","d":false,"s":<SIZE_IN_BYTES>}
or a directory
{"n":"<NAME>","d":true}
{"e":[{"n":"/morph4k-rescue.binc","d":false,"s":1179712},{"n":"/test","d":true}],"r":"/sdcard/fw"}
If an error occurs there will be an "error" attribute added to the reply JSON:
"error":"<ERROR>"
# Request
sdcard={"a":"info","v":["/sdcard/update","morph4k-update.fx.verifiedo",""]}
{"error":"not_found","e":[{"n":"/morph4k-update.fx.md5","d":false,"s":32},{"n":"/morph4k-update-T120-unstable_cd557f76-d323-11ee-905a-671be1da7cc6.fx.verified","d":false,"s":4792987},{"n":"/morph4k-update-T120-3.7.30.13_cd557f76-d323-11ee-905a-671be1da7cc6.fx.verified","d":false,"s":4315835},{"n":"/morph4k-update-T120-3.9.0_cd557f76-d323-11ee-905a-671be1da7cc6.fx.verified","d":false,"s":4792986},{"n":"/morph4k-update.fx.verified","d":false,"s":4770603}],"r":"/sdcard/update"}
Notice, that the reply will still reply with the standard directory listing reply too (in addition to the error message)
ACTION | ARG1 |
---|---|
list |
directory |
directory
: full directory pathReplies with the directory listing reply.
sdcard={"a":"list","v":["/sdcard/presets"]}
Special case for list
:
directory
does not exist or is a file, no error is generated. The element list will simply be empty.ACTION | ARG1 | ARG2 | ARG3 |
---|---|---|---|
remove |
directory | filename | recursive |
directory
: full directory pathfilename
: name of the directory/file to deleterecursive
: -r
to delete directories recursively, empty string otherwiseReplies with the (updated) directory listing reply.
sdcard={"a":"remove","v":["/sdcard/presets","test.ini","-r"]}
ACTION | ARG1 | ARG2 | ARG3 |
---|---|---|---|
mkdir |
directory | dirname | ensure_parent_dirs |
directory
: full directory pathdirname
: name of the directory to create (slashes in the name are creating directory structures, recursive
has to be set in this case)ensure_parent_dirs
: -p
to ensure parent dirs, empty string otherwiseReplies with the (updated) directory listing reply.
sdcard={"a":"mkdir","v":["/sdcard/fw","test","-p"]}
ACTION | ARG1 | ARG2 | ARG3 | ARG4 |
---|---|---|---|---|
mv |
directory | filename | targetpath | overwrite |
directory
: full directory pathfilename
: name of the directory/file to move/renametargetpath
: full path of (new) file/directoryoverwrite
: -o
to overwrite existing file, empty otherwiseReplies with the (updated) directory listing reply.
sdcard={"a":"mv","v":["/sdcard/fw","test","/sdcard/update/testo",""]}
ACTION | ARG1 | ARG2 | ARG3 | ARG4 | ARG5 |
---|---|---|---|---|---|
preset_create |
directory | filename | preset_description | overwrite | create_mask |
directory
: full directory pathfilename
: (file)name of the preset to createpreset_description
: (multiline) preset descriptionoverwrite
: -o
to overwrite existing preset, empty string otherwisecreate_mask
: bitmask of configuration data to save to preset (see here), common values:
0x7FFFFFFF
Full preset0x00000200
Mask only0x00800000
Color correction onlyReplies with the (updated) directory listing reply.
sdcard={"a":"preset_create","v":["/sdcard/presets","Example preset.ini","This is an example preset\\nline2\\nline3","-o","0x7FFFFFFF"]}
ACTION | ARG1 | ARG2 | ARG3 | ARG4 | ARG5 |
---|---|---|---|---|---|
preset_create |
directory | filename | targetpath | overwrite | preset_description |
directory
: full directory pathfilename
: (file)name of the preset to createtargetpath
: full path of (new) preset fileoverwrite
: -o
to overwrite existing preset, empty string otherwisepreset_description
: new preset descriptionReplies with the (updated) directory listing reply.
sdcard={"a":"edit","v":["/sdcard/presets","test.ini","/sdcard/presets/oest.ini","-o","Created at 2024/08/14 13:45:28 CEST\\n- 1920x1080p input\\n- 4K50 output"]}
ACTION | ARG1 | ARG2 | ARG3 |
---|---|---|---|
info |
directory | filename | include_directory_structure |
directory
: full directory pathfilename
: name of the directory/file to get info forinclude_directory_structure
: -f
to include a list of directories (e.g. for use with rename dialog), empty otherwiseCommon to all file types:
{"d":{"f":"<FILENAME>","m":<TYPE>},"r":"<DIRECTORY_CONTEXT>"}
With:
<FILENAME>
name of the file<TYPE>
type of the file:
-2
firmware update file-1
directory0
file<number>
preset file
The number is a bit mask describing the contents of a preset:
PRESET_ZOOM_FACTOR = BIT(0),
PRESET_ASPECT_RATIO = BIT(1),
PRESET_HORIZ_KERNEL = BIT(2),
PRESET_VERT_KERNEL = BIT(3),
PRESET_HORIZ_SCANLINES = BIT(4),
PRESET_VERT_SCANLINES = BIT(5),
PRESET_DEBLUR = BIT(6),
PRESET_SMOOTHING = BIT(8),
PRESET_TATEMASK = BIT(9),
PRESET_GAMMA = BIT(10),
PRESET_ADAPT_HORIZ_SCANLINES = BIT(11),
PRESET_ADAPT_VERT_SCANLINES = BIT(12),
PRESET_SMOOTHING_ENABLED = BIT(13),
PRESET_HORIZ_PRESCALE = BIT(14),
PRESET_VERT_PRESCALE = BIT(15),
PRESET_SHIFT_CROP = BIT(16),
PRESET_DEINTERLACER = BIT(17),
PRESET_BUFFER_MODE = BIT(18),
PRESET_OUTPUT_MODE = BIT(19),
PRESET_BFI = BIT(20),
PRESET_TX = BIT(21),
PRESET_RX = BIT(22),
PRESET_GAMMA_FULL = BIT(23),
PRESET_ALL = INT_MAX,
This mask is also used for the preset_create
command
<DIRECTORY_CONTEXT>
current directory of the fileTYPE == -2
):{"d":{"f":"<FILENAME>","m":-2,"s":<FILESIZE_IN_BYTES>,"fw_r":"<RUNNING_FW_VERSION>","fw_rc":"<RUNNING_FW_CHECKSUM>","fw_s":"<THIS_FILE_FW_VERSION>","fw_sc":"<THIS_FILE_FW_CHECKSUM>"},"r":"<DIRECTORY_CONTEXT>"}
or
{"d":{"f":"<FILENAME>","m":-2,"s":<FILESIZE_IN_BYTES>,"fw_r":"<RUNNING_FW_VERSION>","fw_rc":"<RUNNING_FW_CHECKSUM>","fw_e":"<THIS_FILE_ERROR>"},"r":"<DIRECTORY_CONTEXT>"}
<FILESIZE_IN_BYTES>
filesize in bytes<RUNNING_FW_VERSION>
version of the currently running firmware<RUNNING_FW_CHECKSUM>
checksum of the currently running firmware<THIS_FILE_FW_VERSION>
version of this firmware file<THIS_FILE_FW_CHECKSUM>
checksum of this firmware file<THIS_FILE_ERROR>
only present if an error occured during reading of the firmware, e.g. invalid_fw_file
, which occurs, when there's a device uuid mismatch{"d":{"f":"morph4k-update.fx.verified","p":"morph4k-update.fx.verified","d":"__fx_file__","m":-2,"s":4770603,"fw_r":"3.9.46","fw_rc":"7e1b5dca","fw_s":"3.9.46","fw_sc":"7e1b5dca"},"r":"/sdcard/update"}
{"d":{"f":"morph4k-update-T120.fx","p":"morph4k-update-T120.fx","d":"__fx_file__","m":-2,"s":4315835,"fw_r":"3.9.46","fw_rc":"7e1b5dca","fw_e":"invalid_fw_file"},"r":"/sdcard/update"}
TYPE > 0
):{"d":{"f":"<FILENAME>","d":"<PRESET_DESCRIPTION>","m":<TYPE>,"s":<FILESIZE_IN_BYTES>},"r":"<DIRECTORY_CONTEXT>"}
<FILESIZE_IN_BYTES>
filesize in bytes<PRESET_DESCRIPTION>
preset description (multiline)<TYPE>
preset type (see here){"d":{"f":"trest.ini","p":"trest.ini","d":"Created at 2024/08/14 13:45:28 CEST\n- 1920x1080p input\n- 4K50 output","m":16762751,"s":2716}}
TYPE == 0
):{"d":{"f":"<FILENAME>","m":<TYPE>,"s":<FILESIZE_IN_BYTES>},"r":"<DIRECTORY_CONTEXT>"}
<FILESIZE_IN_BYTES>
filesize in bytesinclude_directory_structure
is set to -f
{"d":{"f":"<FILENAME>","m":<TYPE>,"s":<FILESIZE_IN_BYTES>},"ds":{<DIRECTORY_LIST>}"r":"<DIRECTORY_CONTEXT>"}
<DIRECTORY_LIST>
:
"ds":[{"n":"<NAME>"},{"n":"<NAME>"}]
Order: Directory postordered
{"d":{"f":"test","m":-1},"ds":[{"n":"/update/test"},{"n":"/update"}],"r":"/sdcard/update"}
If your device has credentials disabled, the --digest --user morph4k:password
part can be omitted. Otherwise you have to adjust adjust --user morph4k:password
to your settings (Web pass
)
POST /upload/sdcard/<PATH_ON_SDCARD>
File contents are sent in the POST body without any encoding (plain binary)
Non-existing directories are created if needed, an existing file with the same path is overwritten.
HTTP error: text/plain error message
HTTP 200:
text/plain progress in the form: STATE:PERCENT_DONE
with:
STATE
0
upload in progress
1
upload finished
PERCENT_DONE
: upload progress in percent (of content-length
header)
curl --digest --user morph4k:password -X POST --data-binary @result/morph4k-rescue.binc "http://morph4k.local/upload/sdcard/fw/morph4k-rescue.binc"
POST /preset-apply/sdcard/<PATH_ON_SDCARD>
No POST body
HTTP 200:
text/plain success/error message
with:
OK
preset successfully applied
ERROR_APPLYING_PRESET_PARSE_FAILED
parsing preset file failed
ERROR_APPLYING_PRESET_FILE_ERROR
preset file not found
ERROR_APPLYING_PRESET_VP_CONFIG_FAILED
internal error creating the configuration context
ERROR_APPLYING_PRESET_MEM_ERROR
internal error allocating memory
curl --digest --user morph4k:password -X POST "http://morph4k.local/preset-apply/sdcard/presets/default.ini"
GET /firmware-info
JSON providing firmware and runtime information.
TODO
# Request
curl -s --digest --user morph4k:password "http://morph4k.local/firmware-info" | jq .
// Response
{
"driver_id": "morph4k",
"console_id": "morph",
"dev_mode": true,
"sdcard_mounted": true,
"standby_mode": false,
"device": {
"platform": "individual",
"uuid": "dbe84bfe-1879-11ef-b754-bbc071fd24a9",
"datecode": "20240522-202833",
"iversion": "3.3.0",
"batch": 200,
"tier": "Shiny Edition"
},
"firmware": {
"version": "fx-framework 3.9.46",
"builddate": "(7e1b5dca)",
"bootloader": "v1",
"rescue": "9.0.0",
"channel": "experimental",
"uri": "http://firmware.pixelfx.co/fwd2.php?n=3&uuid=dbe84bfe-1879-11ef-b754-bbc071fd24a9&cv=3.9.46&pid=morph4k&did=morph4k&ver=%s&l="
},
"features": {
"has_analog_out": false,
"has_deblur": false,
"has_gamma.input": true,
"has_gamma.output": true,
"has_slotmask": true,
"has_smoothing": true
},
"user_files": {
"buttons.ini": false,
"gamma.txt": false,
"ir.ini": false,
"modelines.ini": false,
"presets.ini": true,
"screensaver.txt": false
},
"wifi_status": {
"state": "WIFI_SERVICE_STA_CONNECTED",
"ip": "192.168.2.56",
"channel": 11,
"ap_ssid": "MORPH 4K",
"quality": 100,
"rssi": -49,
"max_tx_power": 20,
"sta_ssid": "z2600.f1",
"bandwidth": "HT40",
"ips_connected": -1
},
"heap_info": {
"internal": {
"allocated_blocks": 774,
"free_blocks": 21,
"largest_free_block": 31744,
"minimum_free_bytes": 35088,
"total_allocated_bytes": 243804,
"total_blocks": 795,
"total_free_bytes": 49212
},
"total": {
"allocated_blocks": 1176,
"free_blocks": 43,
"largest_free_block": 5242880,
"minimum_free_bytes": 5253028,
"total_allocated_bytes": 368308,
"total_blocks": 1219,
"total_free_bytes": 5346780
}
}
}
POST /btn/<KEY>/<REPEAT>
With:
<KEY>
:
o
Menu open (if closed) or OK button
c
Cancel/Exit button
u
Up button
d
Down button
l
Left button
r
Right button
<REPEAT>
:
0
"First" button press (no-repeat)
> 0
Subsequent repeated button presses (holding button down)
OK
# Press OK (no repeat)
curl --digest --user morph4k:password -X POST "http://morph4k.local/btn/o/0"