#include <u.h>
#include <libc.h>
#include <ctype.h>
#include <fcall.h>
#include <thread.h>
#include <9p.h>
#include "usb.h"
#include "uvc.h"
#include "dat.h"
#include "fns.h"
typedef struct Param Param;
enum {
PARAMSPEC,
PARAMCT,
PARAMPU,
};
struct Param {
char *name;
int type;
int cs;
int len;
int flag;
char *(*read)(Cam *, int, Param *);
int (*write)(Cam *, int, Param *, char **, int);
void *auxp;
int auxi;
};
void
errorcode(Dev *d, int term)
{
uchar val;
char *str[] = {
"No error",
"Not ready",
"Wrong state",
"Power",
"Out of range",
"Invalid unit",
"Invalid control",
"Invalid Request",
"Invalid value within range"
};
if(usbcmd(d, 0xA1, GET_CUR, VC_REQUEST_ERROR_CODE_CONTROL << 8, (uchar)term, &val, 1) <= 0)
return;
if(val < nelem(str))
werrstr("%s", str[val]);
}
int
infocheck(Cam *c, int term, Param *p)
{
uchar val;
if(usbcmd(c->dev, 0xA1, GET_INFO, p->cs << 8, term, &val, 1) <= 0){ errorcode(c->dev, term); return -1; }
if((val & 1) == 0){
werrstr("GET not supported");
return -1;
}
return 0;
}
char *
pboolread(Cam *c, int term, Param *p)
{
uchar val;
Dev *d;
d = c->dev;
if(infocheck(c, term, p) < 0) return nil;
if(usbcmd(d, 0xA1, GET_CUR, p->cs << 8, term, &val, 1) <= 0){ errorcode(d, term); return nil; }
if(val)
return strdup("true");
return strdup("false");
}
int
pboolwrite(Cam *c, int term, Param *p, char **f, int nf)
{
uchar v0, v1;
if(nf != 1)
return -1;
v0 = cistrcmp(f[0], "false") == 0 || cistrcmp(f[0], "0") == 0 || cistrcmp(f[0], "no") == 0;
v1 = cistrcmp(f[0], "true") == 0 || cistrcmp(f[0], "1") == 0 || cistrcmp(f[0], "yes") == 0;
if(!(v0 ^ v1))
return -1;
if(usbcmd(c->dev, 0x21, SET_CUR, p->cs << 8, term, &v1, 1) <= 0){ errorcode(c->dev, term); return -1; }
return 0;
}
char *
pintread(Cam *c, int term, Param *p)
{
uchar cur[4], min[4], max[4], res[4];
Dev *d;
d = c->dev;
if(infocheck(c, term, p) < 0) return nil;
if(usbcmd(d, 0xA1, GET_CUR, p->cs << 8, term, cur, p->len) < p->len){ errorcode(d, term); return nil; }
if(usbcmd(d, 0xA1, GET_MIN, p->cs << 8, term, min, p->len) < p->len){ errorcode(d, term); return nil; }
if(usbcmd(d, 0xA1, GET_RES, p->cs << 8, term, res, p->len) < p->len){ errorcode(d, term); return nil; }
if(usbcmd(d, 0xA1, GET_MAX, p->cs << 8, term, max, p->len) < p->len){ errorcode(d, term); return nil; }
switch(p->len){
case 1: return smprint("%d %d/%d/%d", (char)cur[0], (char)min[0], (char)res[0], (char)max[0]);
case 2: return smprint("%d %d/%d/%d", (short)GET2(cur), (short)GET2(min), (short)GET2(res), (short)GET2(max));
case 4: return smprint("%d %d/%d/%d", (int)GET4(cur), (int)GET4(min), (int)GET4(res), (int)GET4(max));
}
werrstr("pintread: unimplemented length %d", p->len);
return nil;
}
int
pintwrite(Cam *c, int term, Param *p, char **f, int nf)
{
int v;
char *sp;
uchar buf[4];
if(nf != 1) return -1;
v = strtol(f[0], &sp, 0);
if(*f[0] == 0 || *sp != 0) return -1;
buf[0] = v;
buf[1] = v >> 8;
buf[2] = v >> 16;
buf[3] = v >> 24;
if(usbcmd(c->dev, 0x21, SET_CUR, p->cs << 8, term, buf, p->len) < p->len){ errorcode(c->dev, term); return -1; }
return 0;
}
char *
puintread(Cam *c, int term, Param *p)
{
uchar cur[4], min[4], max[4], res[4];
Dev *d;
d = c->dev;
if(infocheck(c, term, p) < 0) return nil;
if(usbcmd(d, 0xA1, GET_CUR, p->cs << 8, term, cur, p->len) < p->len){ errorcode(d, term); return nil; }
if(usbcmd(d, 0xA1, GET_MIN, p->cs << 8, term, min, p->len) < p->len){ errorcode(d, term); return nil; }
if(usbcmd(d, 0xA1, GET_RES, p->cs << 8, term, res, p->len) < p->len){ errorcode(d, term); return nil; }
if(usbcmd(d, 0xA1, GET_MAX, p->cs << 8, term, max, p->len) < p->len){ errorcode(d, term); return nil; }
switch(p->len){
case 1: return smprint("%ud %ud/%ud/%ud", (uchar)cur[0], (uchar)min[0], (uchar)res[0], (uchar)max[0]);
case 2: return smprint("%ud %ud/%ud/%ud", (ushort)GET2(cur), (ushort)GET2(min), (ushort)GET2(res), (ushort)GET2(max));
case 4: return smprint("%ud %ud/%ud/%ud", (uint)GET4(cur), (uint)GET4(min), (uint)GET4(res), (uint)GET4(max));
}
werrstr("pintread: unimplemented length %d", p->len);
return nil;
}
int
puintwrite(Cam *c, int term, Param *p, char **f, int nf)
{
uint v;
char *sp;
uchar buf[4];
if(nf != 1) return -1;
v = strtoul(f[0], &sp, 0);
if(*f[0] == 0 || *sp != 0) return -1;
buf[0] = v;
buf[1] = v >> 8;
buf[2] = v >> 16;
buf[3] = v >> 24;
if(usbcmd(c->dev, 0x21, SET_CUR, p->cs << 8, term, buf, p->len) < p->len){ errorcode(c->dev, term); return -1; }
return 0;
}
char *
penumread(Cam *c, int term, Param *p)
{
uchar cur[4];
uint val;
if(infocheck(c, term, p) < 0) return nil;
if(usbcmd(c->dev, 0xA1, GET_CUR, p->cs << 8, term, cur, p->len) < p->len){ errorcode(c->dev, term); return nil; }
switch(p->len){
case 1: val = cur[0]; break;
case 2: val = GET2(cur); break;
case 4: val = GET4(cur); break;
default:
werrstr("pintread: unimplemented length %d", p->len);
return nil;
}
if(val >= p->auxi || ((char**)p->auxp)[val] == nil)
return smprint("%d", val);
return smprint("%s", ((char**)p->auxp)[val]);
}
int
penumwrite(Cam *c, int term, Param *p, char **f, int nf)
{
uint i;
uchar buf[4];
if(nf != 1) return -1;
for(i = 0; i < p->auxi; i++)
if(cistrcmp(((char**)p->auxp)[i], f[0]) == 0)
break;
if(i == p->auxi)
return -1;
buf[0] = i;
buf[1] = i >> 8;
buf[2] = i >> 16;
buf[3] = i >> 24;
if(usbcmd(c->dev, 0x21, SET_CUR, p->cs << 8, term, buf, p->len) < p->len){ errorcode(c->dev, term); return -1; }
return 0;
}
char *
pformatread(Cam *c, int, Param *)
{
Format *f;
VSUncompressedFrame *g;
char buf[5];
if(c->pc.bFormatIndex >= c->nformat) goto nope;
f = c->format[c->pc.bFormatIndex];
if(f == nil) goto nope;
if(c->pc.bFrameIndex >= f->nframe) goto nope;
g = f->frame[c->pc.bFrameIndex];
if(g == nil) goto nope;
memcpy(buf, f->desc->guidFormat, 4);
buf[4] = 0;
return smprint("%dx%dx%d-%s", GET2(g->wWidth), GET2(g->wHeight), f->desc->bBitsPerPixel, buf);
nope:
return smprint("#%d,%d", c->pc.bFormatIndex, c->pc.bFrameIndex);
}
void
frameinterval(Cam *c, VSUncompressedFrame *f, double t)
{
double δ, minδ;
int i, mini;
uint min, max, step, val;
if(f->bFrameIntervalType == 0){
min = GET4(f->dwFrameInterval[0]);
max = GET4(f->dwFrameInterval[1]);
step = GET4(f->dwFrameInterval[2]);
if(t <= min)
val = min;
else if(t >= max)
val = max;
else{
val = floor((t - min) / step) * step + min;
if(t >= val + step / 2.0)
t += step;
}
}else{
mini = -1;
for(i = 0; i < f->bFrameIntervalType; i++){
δ = fabs(((u32int)GET4(f->dwFrameInterval[i])) - t);
if(mini < 0 || δ < minδ){
mini = i;
minδ = δ;
}
}
assert(mini >= 0);
val = GET4(f->dwFrameInterval[mini]);
}
PUT4(c->pc.dwFrameInterval, val);
}
int
findres(Cam *c, int w, int h, int fr)
{
Format *f;
VSUncompressedFrame *g;
int i;
if(fr >= c->nformat || (f = c->format[fr]) == nil) return -1;
for(i = 0; i < f->nframe; i++){
g = f->frame[i];
if(g == nil) continue;
if(GET2(g->wWidth) == w && GET2(g->wHeight) == h)
return i;
}
return -1;
}
int
pformatwrite(Cam *c, int, Param *, char **args, int nargs)
{
int w, h, bpp;
char *p;
int i;
int j;
char *q;
Format *f;
if(nargs != 1) return -1;
p = args[0];
if(*p == 0) return -1;
w = strtol(p, &p, 0);
if(*p != 'x') return -1;
h = strtol(p + 1, &q, 0);
if(q == p + 1) return -1;
p = q;
if(*p == 0){
j = c->pc.bFormatIndex;
i = findres(c, w, h, j);
if(i < 0)
for(j = 0; j < c->nformat; j++){
i = findres(c, w, h, j);
if(i >= 0) break;
}
}else{
if(*p != 'x' || *++p == '-') return -1;
bpp = strtol(p, &p, 0);
if(*p != '-') return -1;
if(strlen(p) != 4) return -1;
i = -1;
for(j = 0; j < c->nformat; j++){
if((f = c->format[j]) == nil) continue;
if(f->desc->bBitsPerPixel != bpp) continue;
if(memcmp(f->desc->guidFormat, p, 4) != 0) continue;
i = findres(c, w, h, j);
if(i >= 0) break;
}
}
if(i < 0)
return -1;
if(c->active != 0){
werrstr("camera active");
return -1;
}
c->pc.bFormatIndex = j;
c->pc.bFrameIndex = i;
frameinterval(c, c->format[j]->frame[i], GET4(c->pc.dwFrameInterval));
return 0;
}
char *
pfpsread(Cam *c, int, Param *)
{
if(GET4(c->pc.dwFrameInterval) == 0)
return smprint("?");
return smprint("%.2f", 10e6 / GET4(c->pc.dwFrameInterval));
}
int
pfpswrite(Cam *c, int, Param *, char **args, int nargs)
{
double d, t;
char *sp;
VSUncompressedFrame *f;
if(nargs != 1) return -1;
d = strtod(args[0], &sp);
if(*args[0] == 0 || *sp != 0) return -1;
if(getframedesc(c, c->pc.bFormatIndex, c->pc.bFrameIndex, nil, &f) < 0){
werrstr("invalid format active");
return -1;
}
if(isNaN(d) || isInf(d, 1) || d <= 0) return -1;
if(c->active != 0){
werrstr("camera active");
return -1;
}
t = 10e6 / d;
frameinterval(c, f, t);
return 0;
}
//static char *autoexposure[] = {"manual", "auto", "shutter", "aperture"};
static char *powerlinefrequency[] = {"disabled", "50", "60", "auto"};
static Param params[] = {
{"format", PARAMSPEC, -1, -1, -1, pformatread, pformatwrite},
{"fps", PARAMSPEC, -1, -1, -1, pfpsread, pfpswrite},
{"progressive", PARAMCT, CT_SCANNING_MODE_CONTROL, 1, 0, pboolread, pboolwrite},
// {"auto-exposure-mode", PARAMCT, CT_AE_MODE_CONTROL, 1, 1, pbitread, pbitwrite, autoexposure, nelem(autoexposure)},
{"auto-exposure-priority", PARAMCT, CT_AE_PRIORITY_CONTROL, 1, 2, pboolread, pboolwrite},
{"exposure-time", PARAMCT, CT_EXPOSURE_TIME_ABSOLUTE_CONTROL, 4, 3, puintread, puintwrite},
{"focus", PARAMCT, CT_FOCUS_ABSOLUTE_CONTROL, 2, 5, puintread, puintwrite},
{"focus-simple", PARAMCT, CT_FOCUS_SIMPLE_CONTROL, 1, 19, puintread, puintwrite},
{"focus-auto", PARAMCT, CT_FOCUS_AUTO_CONTROL, 1, 17, pboolread, pboolwrite},
{"iris", PARAMCT, CT_IRIS_ABSOLUTE_CONTROL, 2, 7, puintread, puintwrite},
{"zoom", PARAMCT, CT_ZOOM_ABSOLUTE_CONTROL, 2, 9, puintread, puintwrite},
{"backlight-compensation", PARAMPU, PU_BACKLIGHT_COMPENSATION_CONTROL, 2, 8, puintread, puintwrite},
{"brightness", PARAMPU, PU_BRIGHTNESS_CONTROL, 2, 0, pintread, pintwrite},
{"contrast", PARAMPU, PU_CONTRAST_CONTROL, 2, 1, puintread, puintwrite},
{"contrast-auto", PARAMPU, PU_CONTRAST_AUTO_CONTROL, 1, 18, pboolread, pboolwrite},
{"gain", PARAMPU, PU_GAIN_CONTROL, 2, 9, puintread, puintwrite},
{"powerline-frequency", PARAMPU, PU_POWER_LINE_FREQUENCY_CONTROL, 1, 10, penumread, penumwrite, powerlinefrequency, nelem(powerlinefrequency)},
{"hue", PARAMPU, PU_HUE_CONTROL, 2, 2, pintread, pintwrite},
{"hue-auto", PARAMPU, PU_HUE_AUTO_CONTROL, 1, 11, pboolread, pboolwrite},
{"saturation", PARAMPU, PU_SATURATION_CONTROL, 2, 3, puintread, puintwrite},
{"sharpness", PARAMPU, PU_SHARPNESS_CONTROL, 2, 4, puintread, puintwrite},
{"gamma", PARAMPU, PU_GAMMA_CONTROL, 2, 5, puintread, puintwrite},
{"white-balance-temperature", PARAMPU, PU_WHITE_BALANCE_TEMPERATURE_CONTROL, 2, 6, puintread, puintwrite},
{"white-balance-temperature-auto", PARAMPU, PU_WHITE_BALANCE_TEMPERATURE_AUTO_CONTROL, 1, 12, pboolread, pboolwrite},
};
int
unittype(int i, uchar **ctlp)
{
if(unit[i] == nil)
return -1;
switch(unit[i]->bDescriptorSubtype){
case VC_INPUT_TERMINAL:
if(GET2(((VCInputTerminal*)unit[i])->wTerminalType) == ITT_CAMERA){
if(ctlp != nil) *ctlp = ((VCCameraTerminal*)unit[i])->bmControls;
return PARAMCT;
}
break;
case VC_PROCESSING_UNIT:
if(ctlp != nil) *ctlp = ((VCProcessingUnit*)unit[i])->bmControls;
return PARAMPU;
}
return -1;
}
char *
ctlread(Cam *c)
{
Fmt f;
int i;
int ut;
Param *p;
uchar *bmControls;
int ifid;
char *str;
fmtstrinit(&f);
for(p = params; p < params + nelem(params); p++){
if(p->type != PARAMSPEC) continue;
str = p->read(c, c->iface->id, p);
if(str == nil)
continue;
fmtprint(&f, "0 %s %s\n", p->name, str);
free(str);
}
for(i = 0; i < nunit; i++){
ut = unittype(i, &bmControls);
if(ut < 0) continue;
ifid = unitif[i]->id;
for(p = params; p < params + nelem(params); p++){
if(p->type != ut) continue;
if(bmControls != nil && p->flag >= 0 && (bmControls[p->flag >> 3] & 1<<(p->flag & 7)) == 0)
continue;
str = p->read(c, i << 8 | ifid, p);
if(str == nil)
continue;
fmtprint(&f, "%d %s %s\n", i, p->name, str);
free(str);
}
}
return fmtstrflush(&f);
}
static Param *
findparam(char *s)
{
Param *p;
for(p = params; p < params + nelem(params); p++)
if(strcmp(s, p->name) == 0)
return p;
werrstr("no such parameter");
return nil;
}
static int
unitbytype(int type)
{
int i;
for(i = 0; i < nunit; i++)
if(unittype(i, nil) == type)
return i;
werrstr("no matching unit");
return -1;
}
int
ctlwrite(Cam *c, char *msg)
{
char *f[10], *sp;
uchar *bmControls;
Param *p;
int aut;
int nf;
int uid, ifid;
nf = tokenize(msg, f, nelem(f));
if(nf == nelem(f))
return -1;
uid = strtoul(f[0], &sp, 0);
aut = *f[0] == 0 || *sp != 0;
if(aut){
p = findparam(f[0]);
if(p == nil)
return -1;
if(p->type == PARAMSPEC)
uid = 0;
else
uid = unitbytype(p->type);
if(uid < 0)
return -1;
}else{
p = findparam(f[1]);
if(p == nil)
return -1;
if(p->type != PARAMSPEC && ((uint)uid >= nunit || unit[uid] == nil)){
werrstr("no such unit");
return -1;
}
}
if(p->type != PARAMSPEC){
if(unittype(uid, &bmControls) != p->type){
werrstr("unit does not have this parameter");
return -1;
}
if(bmControls != nil && p->flag >= 0 && (bmControls[p->flag >> 3] & 1<<(p->flag & 7)) == 0){
werrstr("parameter not available");
return -1;
}
ifid = unitif[uid]->id;
}else
ifid = c->iface->id;
if(p->write == nil){
werrstr("read-only parameter");
return -1;
}
return p->write(c, uid << 8 | ifid, p, f + (2 - aut), nf - (2 - aut));
}