Open tm-robinson opened 6 years ago
Ok I have managed to get strace cross compiled and running using the SDK. This was the method I used that worked:
/opt/hisi-linux-nptl/arm-hisiv100-linux/target/bin
contains all the compiler binaries e.g. arm-hisiv100nptl-linux-gcc
export PATH=/opt/hisi-linux-nptl/arm-hisiv100-linux/target/bin:$PATH
export ARCH=arm
export CROSS_COMPILE=arm-hisiv100nptl-linux-
(Not sure if these are required or not)
~/buildroot-2018.05/output/build/strace-4.21
)./configure --host=arm-hisiv100nptl-linux
make
strace
binary to the camera using FTPstrace
to one of the existing processes e.g. to rmm
:
/tmp/sd # ./strace -p 923
./strace: Process 923 attached
nanosleep(0xfffffffc, 0xbef0caa0) = 0
open("/dev/isp_dev", O_RDONLY) = 28
ioctl(28, _IOC(_IOC_WRITE, 0x49, 0, 0x4), 0xbef0c234) = 0
ioctl(28, _IOC(_IOC_WRITE, 0x49, 0xa, 0x4), 0xbef0c238) = 0
ioctl(28, _IOC(_IOC_READ, 0x49, 0x9, 0x4), 0xbef0c23c) = 0
mmap2(NULL, 16384, PROT_READ|PROT_WRITE, MAP_SHARED, 10, 0x81621000) = 0xb5f70000
munmap(0xb5f70000, 16384) = 0
ioctl(28, _IOC(_IOC_WRITE, 0x49, 0xa, 0x4), 0xbef0c238) = 0
close(28) = 0
rt_sigaction(SIGCHLD, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
nanosleep({tv_sec=1, tv_nsec=0}, 0xbef0caa0) = 0
open("/dev/isp_dev", O_RDONLY) = 28
ioctl(28, _IOC(_IOC_WRITE, 0x49, 0, 0x4), 0xbef0c234) = 0
ioctl(28, _IOC(_IOC_WRITE, 0x49, 0xa, 0x4), 0xbef0c238) = 0
ioctl(28, _IOC(_IOC_READ, 0x49, 0x9, 0x4), 0xbef0c23c) = 0
mmap2(NULL, 16384, PROT_READ|PROT_WRITE, MAP_SHARED, 10, 0x81621000) = 0xb5f70000
munmap(0xb5f70000, 16384) = 0
ioctl(28, _IOC(_IOC_WRITE, 0x49, 0xa, 0x4), 0xbef0c238) = 0
close(28) = 0
rt_sigaction(SIGCHLD, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
nanosleep({tv_sec=1, tv_nsec=0}, ^C./strace: Process 923 detached
<detached ...>
Initial guess is that rmm is opening the camera device (/dev/isp_dev
?), reading a still image (the ioctl
and mmap2
commands), closing the camera device and then sleeping for 1 second. I assume it compares the still image with the previous still image to detect motion.
I am not sure how the output of this gets fed into other processes though, as from the strace output, rmm doesn't seem to be writing to any queues. However the dispatch
process does receive data from the /ipc_dispatch
queue including some of the messages mentioned in my previous post.
@tm-robinson did you get any further with this, I'd like to see if it's possible to trigger the PTZ motors using the same method?
@xarnze I haven't got much further, apart from a few attempts to compile and grab still images directly, firstly using v4l2grab (which compiles, using this toolchain: https://github.com/mark4h/hi35xx-buildroot but isn't able to open the device (I'm not sure which device to use, the possible choices all seem to give "inappropriate ioctl" errors)) and then using vi_bayerdump (at https://github.com/mark4h/HI3518-SC1035/tree/master/vi_bayerdump_SC1035 ) which fails with i2c errors.
I assume you are referring to the PTZ motors in the Yi Dome? I was actually attempting all of the above on a Yi Home. I do also own a Yi Dome but haven't tried to flash it yet, so haven't got as far as examining what additional IPC messages are being sent around on there (if any). I never really finished fully investigating the IPC messages on the Yi Home either, and never attempted to send any fake ones to see what would happen (although the dump_mq.c code above has all the calls necessary to try this). So that's the next thing on the list if I get more time to play with this.
If anyone has time to write a direct frame grabber using the strace output I posted above (with the mmap calls) that would be great, as I think this would probably work to grab still images from the sensor, as the rmm process seems to be doing it fairly frequently but it always closes the device after each grab. So I can't see why a second process performing its own frame grab wouldn't work.
I've decompiled the rmm binary and it if you search for zbar_scan_image that looks like the code that gets the image, theres also a lot in there to do with sending mq messages could be worth a look anyway. rmm.asm.zip
I've tried killing everything but p2p_tnp
and controlling the motors, they stopped moving after killing dispatch
but the /ipc_dispatch
started filling up every time you pressed the buttons, the capture is below
On /ipc_dispatch
// Up
Sat Jul 28 09:53:11 2018 SERVER: Received message len 16: '' (in hex:'01:00:00:00:08:00:00:00:81:00:01:00:00:00:00:00')
Sat Jul 28 09:53:11 2018 SERVER: Received message len 16: '' (in hex:'01:00:00:00:08:00:00:00:81:00:01:00:00:00:00:00')
Sat Jul 28 09:53:11 2018 SERVER: Received message len 20: '' (in hex:'01:00:00:00:01:00:00:00:0E:40:0E:40:00:00:00:00:01:00:00:00')
Sat Jul 28 09:53:11 2018 SERVER: Received message len 16: '' (in hex:'01:00:00:00:08:00:00:00:81:00:01:00:00:00:00:00')
Sat Jul 28 09:53:12 2018 SERVER: Received message len 16: '' (in hex:'01:00:00:00:08:00:00:00:E2:00:01:00:00:00:00:00')
Sat Jul 28 09:53:12 2018 SERVER: Received message len 20: '' (in hex:'01:00:00:00:08:00:00:00:E4:00:01:00:04:00:00:00:00:00:00:00')
Sat Jul 28 09:53:12 2018 SERVER: Received message len 16: '' (in hex:'01:00:00:00:08:00:00:00:E1:00:01:00:00:00:00:00')
Sat Jul 28 09:53:12 2018 SERVER: Received message len 20: '' (in hex:'01:00:00:00:08:00:00:00:7F:00:01:00:04:00:00:00:05:00:00:00')
Sat Jul 28 09:53:12 2018 SERVER: Received message len 20: '' (in hex:'01:00:00:00:08:00:00:00:80:00:01:00:04:00:00:00:01:00:00:00')
Sat Jul 28 09:53:13 2018 SERVER: Received message len 16: '' (in hex:'01:00:00:00:08:00:00:00:8F:00:01:00:00:00:00:00')
Sat Jul 28 09:53:13 2018 SERVER: Received message len 20: '' (in hex:'01:00:00:00:08:00:00:00:E3:00:01:00:04:00:00:00:01:00:00:00')
Sat Jul 28 09:53:18 2018 SERVER: Received message len 24: '' (in hex:'01:00:00:00:08:00:00:00:06:40:06:40:18:00:00:00:01:00:00:00:00:00:00:00')
Sat Jul 28 09:53:18 2018 SERVER: Received message len 16: '' (in hex:'01:00:00:00:08:00:00:00:07:40:01:00:00:00:00:00')
Sat Jul 28 09:53:20 2018 SERVER: Received message len 16: '' (in hex:'01:00:00:00:08:00:00:00:81:00:01:00:00:00:00:00')
Sat Jul 28 09:53:21 2018 SERVER: Received message len 20: '' (in hex:'01:00:00:00:01:00:00:00:0E:40:0E:40:00:00:00:00:01:00:00:00')
// Down
Sat Jul 28 09:53:33 2018 SERVER: Received message len 16: '' (in hex:'01:00:00:00:08:00:00:00:81:00:01:00:00:00:00:00')
Sat Jul 28 09:53:37 2018 SERVER: Received message len 24: '' (in hex:'01:00:00:00:08:00:00:00:06:40:06:40:18:00:00:00:02:00:00:00:00:00:00:00')
Sat Jul 28 09:53:37 2018 SERVER: Received message len 16: '' (in hex:'01:00:00:00:08:00:00:00:07:40:01:00:00:00:00:00')
Sat Jul 28 09:53:40 2018 SERVER: Received message len 16: '' (in hex:'01:00:00:00:08:00:00:00:81:00:01:00:00:00:00:00')
// Left
Sat Jul 28 09:54:04 2018 SERVER: Received message len 16: '' (in hex:'01:00:00:00:10:00:00:00:E7:00:E7:00:00:00:00:00')
Sat Jul 28 09:54:04 2018 SERVER: Received message len 16: '' (in hex:'01:00:00:00:08:00:00:00:81:00:01:00:00:00:00:00')
Sat Jul 28 09:54:07 2018 SERVER: Received message len 24: '' (in hex:'01:00:00:00:08:00:00:00:06:40:06:40:18:00:00:00:03:00:00:00:00:00:00:00')
Sat Jul 28 09:54:07 2018 SERVER: Received message len 16: '' (in hex:'01:00:00:00:08:00:00:00:07:40:01:00:00:00:00:00')
// Right
Sat Jul 28 09:53:51 2018 SERVER: Received message len 16: '' (in hex:'01:00:00:00:08:00:00:00:81:00:01:00:00:00:00:00')
Sat Jul 28 09:53:53 2018 SERVER: Received message len 20: '' (in hex:'01:00:00:00:01:00:00:00:0E:40:0E:40:00:00:00:00:01:00:00:00')
Sat Jul 28 09:53:53 2018 SERVER: Received message len 24: '' (in hex:'01:00:00:00:08:00:00:00:06:40:06:40:18:00:00:00:04:00:00:00:00:00:00:00')
Sat Jul 28 09:53:53 2018 SERVER: Received message len 16: '' (in hex:'01:00:00:00:08:00:00:00:07:40:01:00:00:00:00:00')
Next thing to try will be adding one of these messages to the queue and seeing if the camera moves
Start moving left by sending:
unsigned char MsgA[] = "0100000010000000E700E70000000000";
unsigned char MsgB[] = "01000000080000008100010000000000";
Stop moving left by sending:
unsigned char MsgC[] = "010000000800000006400640180000000300000000000000";
unsigned char MsgD[] = "01000000080000000740010000000000";
Start moving right by sending:
unsigned char MsgA[] = "01000000080000008100010000000000";
unsigned char MsgB[] = "01000000010000000E400E400000000001000000";
Stop moving right by sending:
unsigned char MsgC[] = "010000000800000006400640180000000400000000000000";
unsigned char MsgD[] = "01000000080000000740010000000000";
Still looking at this one but looks to need more messages than all the other movements.
Start moving down by sending:
unsigned char downMsgA[] = "01000000080000008100010000000000";
unsigned char downMsgB[] = "010000000800000006400640180000000200000000000000";
Stop moving by sending:
unsigned char downMsgC[] = "01000000080000000740010000000000";
unsigned char downMsgD[] = "01000000080000008100010000000000";
This code moves the camera down a bit by sending the start moving mq packets above, to the /icp_dispatch
queue:
#include <fcntl.h>
#include <mqueue.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <time.h>
#include <stdarg.h>
#define QUEUE_NAME "/test_queue"
#define MAX_SIZE 1024
#define MSG_STOP "exit"
unsigned char HexChar (char c)
{
if ('0' <= c && c <= '9') return (unsigned char)(c - '0');
if ('A' <= c && c <= 'F') return (unsigned char)(c - 'A' + 10);
if ('a' <= c && c <= 'f') return (unsigned char)(c - 'a' + 10);
return 0xFF;
}
int HexToBin (const char* s, unsigned char * buff, int length)
{
int result;
if (!s || !buff || length <= 0) return -1;
for (result = 0; *s; ++result)
{
unsigned char msn = HexChar(*s++);
if (msn == 0xFF) return -1;
unsigned char lsn = HexChar(*s++);
if (lsn == 0xFF) return -1;
unsigned char bin = (msn << 4) + lsn;
if (length-- <= 0) return -1;
*buff++ = bin;
}
return result;
}
void BinToHex (const unsigned char * buff, int length, char * output, int outLength)
{
char binHex[] = "0123456789ABCDEF";
if (!output || outLength < 4) return;
*output = '\0';
if (!buff || length <= 0 || outLength <= 2 * length)
{
memcpy(output, "ERR", 4);
return;
}
for (; length > 0; --length, outLength -= 2)
{
unsigned char byte = *buff++;
*output++ = binHex[(byte >> 4) & 0x0F];
*output++ = binHex[byte & 0x0F];
}
if (outLength-- <= 0) return;
*output++ = '\0';
}
void writelog(FILE *f, const char *fmt, ...) {
va_list args;
va_start(args, fmt);
vprintf(fmt, args);
vfprintf(f, fmt, args);
fflush(f);
fflush(stdout);
va_end(args);
}
void * queue_receiver(void *queuename) {
mqd_t mq;
ssize_t bytes_read;
time_t ltime;
unsigned char buffer[MAX_SIZE + 1];
FILE *f;
f = fopen("dump_mq.log", "a+");
writelog(f, "SERVER: Openening queue '%s'\n", (char*) queuename);
/* open the message queue */
mq = mq_open((char*) queuename, O_RDWR); // | O_NONBLOCK);
if (f == NULL) {
printf("Error opening logfile\n");
exit(1);
}
memset(buffer, 0x00, sizeof(buffer));
unsigned char downMsgA[] = "01000000080000008100010000000000";
unsigned char downMsgB[] = "010000000800000006400640180000000200000000000000";
unsigned char downMsgC[] = "01000000080000000740010000000000";
unsigned char downMsgD[] = "01000000080000008100010000000000";
writelog(f, "SERVER: Sending A \n");
HexToBin(downMsgA, (unsigned char*)buffer, 32);
mq_send(mq, buffer, 24, 0);
usleep(0.1 * 1e6);
writelog(f, "SERVER: Sending B \n");
HexToBin(downMsgB, (unsigned char*)buffer, 48);
mq_send(mq, buffer, 24, 0);
usleep(0.3 * 1e6);
writelog(f, "SERVER: Sent move \n");
writelog(f, "SERVER: Sending C \n");
HexToBin(downMsgC, (unsigned char*)buffer, 32);
mq_send(mq, buffer, 24, 0);
usleep(0.1 * 1e6);
writelog(f, "SERVER: Sending D \n");
HexToBin(downMsgD, (unsigned char*)buffer, 32);
mq_send(mq, buffer, 24, 0);
usleep(0.1 * 1e6);
writelog(f, "SERVER: Sent stop move \n");
/* cleanup */
mq_close(mq);
fclose(f);
return NULL;
}
int main(int argc, char** argv) {
pthread_t client, server;
printf("Start...\n");
pthread_create(&server, NULL, &queue_receiver, argv[1]);
pthread_join(server, NULL);
printf("Done...\n");
return (EXIT_SUCCESS);
}
@tm-robinson How did you snag the arm-hisiv100nptl-linux-gcc compiler? I've got the SDK and unpacked it as shadow-1 outlined in #23, but I'm having a hard time accessing that compiler and id love to be able to play around with motion triggering on the Yi dome.
@tm-robinson
Ive made some progress that you may be excited to hear about!
to trigger video recording, you must send a message to the /ipc_dispatch while blocking messages through the /ipc_rmm queue (*this does not seem necessary). To stop recording, you must send another message to the /ipc_dispatch queue.
How I achieved this was via running your listening script on /ipc_rmm queue, then running @xarnze 's injection version with the following replacement:
unsigned char startMsg[] = "01000000020000007C007C0000000000";
unsigned char endMsg[] = "01000000020000007D007D0000000000";
writelog(f, "SERVER: start \n");
HexToBin(startMsg, (unsigned char*)buffer, 32);
mq_send(mq, buffer, 16, 0);
usleep(100 * 1e6);
writelog(f, "SERVER: stop \n");
HexToBin(endMsg, (unsigned char*)buffer, 32);
mq_send(mq, buffer, 16, 0);
usleep(0.1 * 1e6);
in place of the movement messages. If you have an SD card that can save the motion video (for example, a SD card formatted by the YI, cards with the initial hack dont seem to record video) you will then find non-motion triggered mp4's sitting prime and pretty in the sd card. I'm working on a script to automate this and curl short videos from the camera on regular intervals, I'll throw it up here when it's done.
*i figured out the compiler also, accidentally skipped a few steps in the MAKE of the sdk.
I've built luajit.gz which may speed/ease the development. E.g. luajit ipc.lua
:
--[[
Copyright 2018 Vladimir Dronnikov
GPL
]]
local ffi = require "ffi"
ffi.cdef[[
int printf(const char *fmt, ...);
void perror(const char *s);
void usleep(unsigned long usec);
int open(const char *pathname, int flags);
int read(int fd, const void *buf, unsigned count);
int write(int fd, const void *buf, unsigned count);
int close(int fd);
char *mmap (void *start, unsigned int length, int prot, int flags, int fd, unsigned int offset);
char *mmap2(void *start, unsigned int length, int prot, int flags, int fd, unsigned int pgoffset);
int munmap(void *start, unsigned int length);
char *memmem(const void *haystack, unsigned int haystacklen, const void *needle, unsigned int needlelen);
int memcmp(const void *s1, const void *s2, unsigned int n);
int mq_open(const char *name, int oflag);
int mq_send(int mqdes, const void *msg_ptr, unsigned int msg_len, unsigned msg_prio);
int mq_receive(int mqdes, const void *msg_ptr, unsigned int msg_len, unsigned *msg_prio);
]]
local MQ = ffi.load("/home/lib/librt-0.9.33.2.so")
local mq = MQ.mq_open(arg[1] or "/ipc_dispatch", 0x02)
print(mq, ffi.C.perror("mq_open"))
local buf = ("-"):rep(1024)
local startmsg = "\x01\x00\x00\x00\x02\x00\x00\x00\x7C\x00\x7C\x00\x00\x00\x00\x00"
local endmsg = "\x01\x00\x00\x00\x02\x00\x00\x00\x7D\x00\x7D\x00\x00\x00\x00\x00"
--[[
-- start
MQ.mq_send(mq, startmsg, #startmsg, 0)
-- wait
ffi.C.usleep(2000000)
-- stop
MQ.mq_send(mq, endmsg, #endmsg, 0)
]]
-- dump
while true do
-- local len = ffi.C.read(mq, buf, #buf)
local len = MQ.mq_receive(mq, buf, #buf, nil)
if len == -1 then
print(len, ffi.C.perror("read"))
break
end
if len > 0 then
io.stdout:write(("%5d "):format(len))
for i = 1, len do
io.stdout:write(("%02x"):format(buf:byte(i)))
end
io.stdout:write("\n")
end
ffi.C.usleep(200000)
end
$ tcpsvd -l 0 -v 0.0.0.0 4445 /tmp/sd/luajit /tmp/sd/ipc_dispatch.lua
--[[
Copyright 2018 Vladimir Dronnikov
GPL
]]
local ffi = require "ffi"
ffi.cdef[[
int printf(const char *fmt, ...);
void perror(const char *s);
void usleep(unsigned long usec);
int open(const char *pathname, int flags);
int read(int fd, const void *buf, unsigned count);
int write(int fd, const void *buf, unsigned count);
int close(int fd);
char *mmap (void *start, unsigned int length, int prot, int flags, int fd, unsigned int offset);
char *mmap2(void *start, unsigned int length, int prot, int flags, int fd, unsigned int pgoffset);
int munmap(void *start, unsigned int length);
char *memmem(const void *haystack, unsigned int haystacklen, const void *needle, unsigned int needlelen);
int memcmp(const void *s1, const void *s2, unsigned int n);
int mq_open(const char *name, int oflag);
int mq_send(int mqdes, const void *msg_ptr, unsigned int msg_len, unsigned msg_prio);
int mq_receive(int mqdes, const void *msg_ptr, unsigned int msg_len, unsigned *msg_prio);
]]
-- open queue
local MQ = ffi.load("/home/lib/librt-0.9.33.2.so")
local mq = MQ.mq_open("/ipc_dispatch", 0x02)
assert(mq ~= -1, ffi.C.perror("mq_open"))
-- helpers
function string.fromhex(str)
return (str:gsub("..", function(cc)
return string.char(tonumber(cc, 16))
end))
end
function string.tohex(str)
return (str:gsub(".", function (c)
return string.format("%02X", string.byte(c))
end))
end
-- read hex string input, write bin to queue
while true do
local line = io.read("*line")
if line == nil then break end
local bin = line:fromhex()
MQ.mq_send(mq, bin, #bin, 0)
end
$ echo "01000000020000007C007C0000000000" | nc camera 4445
$ echo "01000000020000007D007D0000000000" | nc camera 4445
I've managed to get yi-hack-v3 up and running on a YiHome 720p (the 47US variant) and it's all working nicely. I can access the camera via telnet/ftp and download the MP4 files that are recorded whenever motion is detected.
I also want to be able to "trigger" the recording of an MP4 file at an arbitrary time (maybe on a schedule, or from some other external event that I can wire up via telnet later).
I thought of doing this via the existing processes that are already running on the camera, to keep it as non-invasive as possible, as I still want to use the camera in the "normal" way, to look at live video and play recorded video via the iPhone/android/native apps.
Through examining the running processes via ps, lsof and /proc, and looking at the log file (/tmp/log.txt) I have noticed a few things:
The main processes that run that seem to be of interest are:
mp4record
- I assume it does what the name suggests, i.e. records an mp4 file whenever motion is detected. In log.txt it writes messages such as "main stream record finish", which makes sense.dispatch
- from the name it sounds like some kind of "controlling" process that triggers tasks for other processes to complete.rmm
- maybe motion detection as it writes messages into the log like "got a new motion start" (after which the dispatch process usually writes "g_dispatch_info.mmap_info->motion_type=0"cloud
- presumably to do with syncing recording metadata into the cloud? or responding to requests for connections from the mobile/desktop apps?p2p_tnp
- not sure about this one but maybe it handles sending data to apps once connected?watch_process
- seems to be responsible for ensuring all the other processes are running, and restarting them if any of them die(If anyone has any more info on the above or can make any corrections please do!)
By looking at lsof I can see that these processes are communicating via a number of POSIX mqueue message queues:
However if you check these in /dev/mqueue they seem to be always empty e.g.:
I wondered if these were indeed being used to communicate between processes, so as an experiment I killed the
mp4record
process (and then thewatch_process
process to stop it from restarting it 👍 ).Sure enough, when I waved my hand in front of the camera, the ipc_dispatch queue seemed to start to fill with messages.
I thought it would be interesting to examine the contents of these messages and observe exactly when they were being sent. There are no command line tools to interact with the POSIX message queues, so I wrote a small C program to dump the messages from a particular queue:
This compiles with the Hi3518E V100R001C01SPC0B0 SDK using the command:
arm-hisiv100nptl-linux-gcc dump_mq.c -o dump_mq -lrt -lpthread
And then when FTP'd onto the camera, it seems to run fine and dumps out messages from the queue (specified on the command line) onto the console and also into a log file called dump_mq.log. This screws up the camera behaviour as other processes that were expecting to receive messages now don't receive them as they have been grabbed by the dump_mq process. I did experiment (as can be seen in the code) with grabbing the messages and then immediately writing them back onto the same queue again after they've been examined, and then waiting 0.1 seconds to ensure the same message isn't written back straight away, but that has the possibility of "missing" messages as other processes could get there first (which could happen anyway, but sleeping makes it more likely I suppose).
So far I've observed /ipc_rmm seems to get a message whenever there is a motion detection event. This message (hex dumped) comes in at the same time (roughly) as the log entry below:
And then /ipc_dispatch seems to contain pretty regular messages which I think may be heartbeats from processes like mp4record (looking at the log entries that show up at similar times):
This is as far as I've got. I'd like to be able to find out more about what all the processes are doing that cause them to send the messages and confirm my assumptions about what is causing various messages to be sent. I assume I could use
strace
for this but I'm not sure how to cross-compile it (I have the source code in a buildroot project but need to compile it using the Hi3518 SDK, not sure how to do this yet).It would also be great if I had a way to understand what the contents of the messages means. They don't print nicely using printf (hence the empty quotemarks in the pasted messages above) so I've hex dumped them. The /ipc_rmm messages do contain the text '/tmp/motion.jpg' which could be a clue (I converted them using https://www.rapidtables.com/convert/number/hex-to-ascii.html ) but the /ipc_dispatch ones don't seem to contain anything immediately useful.
If I'm going to create "fake" messages to put onto one of the ipc queues to trigger a detected motion, I expect I need another message to also trigger the "end" of the motion, otherwise the mp4 recording will continue forever. I haven't yet observed any message that looks like an example of a 'motion stopped' message.
Hopefully someone can build on the above or suggest some next steps. In the meantime I'll keep playing and post any further updates here.
Finally if anyone has any suggestions for better ways to tackle this, in a nice non-invasive manner, that would be good. I had considered trying to directly capture an image from the sensor (along the lines of this: https://mark4h.blogspot.com/2017/09/hi3518-camera-module-part-3-capturing.html or this https://github.com/mark4h/HI3518-SC1035 or potentially this https://github.com/samtap/fang-hacks/issues/87 ) but I am nervous that because the Yi apps are still running, they would still have the sensor "open" and these other tools would be unable to access it, so I haven't put any effort into trying to get these to compile etc yet.