use crate::pipelines::CameraHash;
use crate::search;
use crate::search::{fourcc_from_term, sanitize_name, mbus_from_term};
use crate::solver;
use crate::solver::{list_to_vec, get_term_args, get_u16, get_u32};
use logru_arithmetic::ArithmeticResolver;
use logru_arithmetic::logru;
use logru::resolve::ResolverExt;
use logru::textual::{ParseError, TextualUniverse, UniverseQuery};
use media_subsystem::{EntityName, EntityId};
use std::fmt;
use v4l::FourCC;
use v4l2_subdev::MediaBusFmt;
pub struct ConfigsDatabase {
device: CameraHash,
pipeline: EntityName,
universe: TextualUniverse,
}
impl ConfigsDatabase {
pub fn new(device: CameraHash, pipeline: EntityName, universe:TextualUniverse) -> Self {
Self {device, pipeline, universe}
}
pub fn query_configs(&self, q: ConfigRequest) -> ConfigSolutions<'_> {
self.query_str_configs(&q.as_query(&self.pipeline))
.expect("Malformed query is a bug")
}
pub fn query_str_configs<'a>(&'a self, q: &str) ->
Result<ConfigSolutions<'a>, ParseError>
{
self.query_str(q).map(|solutions| ConfigSolutions {
solutions,
device: self.device.clone(),
})
}
pub fn query_str(&self, q: &str) -> Result<Solutions, ParseError> {
Ok(Solutions::new(
&self.universe,
self.universe.prepare_query(q)?,
))
}
}
impl From<ConfigsDatabase> for TextualUniverse {
fn from(value: ConfigsDatabase) -> Self {
value.universe
}
}
#[derive(Debug)]
pub struct ConfigSolutions<'a>{
solutions: Solutions<'a>,
device: CameraHash,
}
impl<'a> ConfigSolutions<'a> {
pub fn iter(&'a self) -> impl Iterator<Item=DeviceConfig> + use<'a> {
self.solutions.iter()
.filter_map(Solution::into_pipeline_config)
.map(|config| DeviceConfig {
device: self.device.clone(),
config,
})
}
}
use logru::resolve::{OrElse, RuleResolver};
pub struct Solutions<'a> {
resolver: OrElse<ArithmeticResolver, RuleResolver<'a>>,
query: UniverseQuery<'a>,
}
impl<'a> fmt::Debug for Solutions<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Solutions(opaue iterator)")
}
}
impl<'a> Solutions<'a> {
fn new(universe: &'a TextualUniverse, mut query: UniverseQuery<'a>) -> Self {
let resolver = ArithmeticResolver::new(query.symbols_mut()).or_else(universe.resolver());
Self { resolver, query }
}
pub fn iter(&'a self) -> impl Iterator<Item=Solution<'a>> {
use logru::search::query_dfs;
let names = self.query.query().scope.as_ref().unwrap();
query_dfs(self.resolver.clone(), &self.query.query())
.map(|solution| Solution {
query: &self.query,
solution: solver::solution_to_hash(names, solution),
})
}
}
pub struct Solution<'a> {
query: &'a UniverseQuery<'a>,
solution: solver::SolutionHash,
}
impl<'a> Solution<'a> {
pub fn as_string(&self) -> String {
self.solution.iter()
.map(|(var, term)| format!(
"{} = {},",
var,
term
.as_ref()
.map(|term| self.query.pretty().term_to_string(&term))
.unwrap_or_else(|| "_".into())
))
.collect()
}
pub fn into_pipeline_config(self) -> Option<PipelineState> {
self.as_pipeline_config()
}
pub fn as_pipeline_config(&self) -> Option<PipelineState> {
self.toconf(
&self.solution.get("Config").cloned().and_then(|term| term)?
)
}
fn toconf(&self, term: &logru::ast::Term) -> Option<PipelineState> {
let terms = list_to_vec(self.query, term)?;
let pads = terms[..terms.len()-1].iter()
.map(|term| {
let [id, pad_idx, outfmt] = get_term_args(self.query, term, "padcfg")?;
let [mbus, width, height] = get_term_args(self.query, outfmt, "format")?;
Some(PadState {
id: EntityId(get_u32(id)?),
pad_idx: get_u16(pad_idx)?,
mbus: mbus_from_term(self.query, mbus)?,
width: get_int(width)?,
height: get_int(height)?,
})
})
.collect::<Option<Vec<PadState>>>()?;
let [id, pad_idx, outfmt]
= get_term_args(self.query, terms.get(terms.len() - 1)?, "outcfg")?;
let [fourcc, width, height] = get_term_args(self.query, outfmt, "videoformat")?;
Some(PipelineState {
pads,
video_entity: VideoState {
id: EntityId(get_u32(id)?),
pad_idx: get_u16(pad_idx)?,
fourcc: fourcc_from_term(self.query, fourcc)?,
width: get_int(width)?,
height: get_int(height)?,
},
})
}
pub fn get_term(&self, variable: &str) -> Option<&logru::ast::Term> {
self.solution.get(variable).and_then(|term| term.as_ref())
}
}
pub fn get_int(term: &logru::ast::Term) -> Option<u32> {
match term {
logru::ast::Term::Int(val) => Some(*val as u32),
_ => None,
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct DeviceConfig {
device: CameraHash,
config: PipelineState,
}
impl DeviceConfig {
pub fn config(&self) -> &PipelineState {
&self.config
}
pub fn as_config(&self) -> Config {
self.config.as_config()
}
pub fn device(&self) -> &CameraHash {
&self.device
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct PadState {
pub id: EntityId,
pub pad_idx: u16,
pub mbus: MediaBusFmt,
pub width: u32,
pub height: u32,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct PipelineState {
pub pads: Vec<PadState>,
video_entity: VideoState,
}
impl PipelineState {
pub fn as_config(&self) -> Config {
self.video_entity.as_config()
}
}
#[derive(Debug, Clone, PartialEq, Hash, Eq)]
pub struct VideoState {
id: EntityId,
pad_idx: u16,
fourcc: FourCC,
width: u32,
height: u32,
}
impl VideoState {
pub fn as_config(&self) -> Config {
Config {
fourcc: self.fourcc,
width: self.width,
height: self.height,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Config {
pub fourcc: FourCC,
pub width: u32,
pub height: u32,
}
#[derive(Clone, Debug, PartialEq, Eq, Default)]
pub struct ConfigRequest {
pub fourcc: Option<FourCC>,
pub width: Option<u32>,
pub height: Option<u32>,
}
fn term_or_wildcard(val: Option<String>) -> String {
val.unwrap_or("_".into())
}
impl ConfigRequest {
fn as_query(&self, sensor_device: &EntityName) -> String {
format!(
"config_path_by_name(dev_{device}, videoformat({fourcc}, {width}, {height}), Config).",
device=sanitize_name(sensor_device),
fourcc=term_or_wildcard(self.fourcc.map(search::fourcc_to_predicate)),
width=term_or_wildcard(self.width.map(|v| v.to_string())),
height=term_or_wildcard(self.height.map(|v| v.to_string())),
)
}
}
#[cfg(test)]
mod test {
use crate::pipelines::CameraHash;
use std::collections::HashSet;
use v4l::FrameSize;
use v4l::framesize;
use v4l::framesize::FrameSizeEnum;
use super::*;
type Dimensions = (u32, u32);
fn discrete(fourcc: FourCC, width: u32, height: u32) -> FrameSize {
FrameSize { fourcc, size: FrameSizeEnum::Discrete(framesize::Discrete { width, height }) }
}
fn stepwise(
fourcc: FourCC,
(min_width, max_width, step_width): (u32, u32, u32),
(min_height, max_height, step_height): (u32, u32, u32)
) -> FrameSize {
FrameSize {
fourcc,
size: FrameSizeEnum::Stepwise(framesize::Stepwise {
min_width,
max_width,
step_width,
min_height,
max_height,
step_height,
}),
}
}
fn db_with(configs: Vec<FrameSize>) -> ConfigsDatabase {
ConfigsDatabase::new(
CameraHash::default(),
EntityName::Text("test_sensor".into()),
{
let mut universe = TextualUniverse::new();
universe.load_str(solver::STDLIB).unwrap();
universe.load_str(search::TOPOLOGYLIB).unwrap();
universe.load_str("
% this normally comes from device descriptions store.
% In case of MC-centric driver, from video device
source(
dev_test_sensor,
0, mbus_FIXED, W, H
).
% this normally comes from media topology
entity(dev_test_sensor, 111, sensor).
entity(dev_test_video, 222, io).
% id, entity, type, index, must_connect
pad(1111, 111, source, 0, true).
pad(2222, 222, sink, 0, true).
% id, padfrom, padto, enabled
link(_, 1111, 2222, enabled).
% not sure if this is actually used in search
interfacelink(1, some_interface_id, 222, enabled).
").unwrap();
let video_name = EntityName::Text("test_video".into());
universe.load_str(&search::mbus_all_guesses(&video_name)).unwrap();
search::framesizes_as_facts(&video_name, configs)
.into_iter()
.for_each(|cfg| universe.load_str(dbg!(&cfg)).unwrap());
universe
},
)
}
fn db() -> ConfigsDatabase {
db_with(vec![
discrete(FourCC::new(b"YUYV"), 640, 480),
discrete(FourCC::new(b"RGBA"), 640, 480),
discrete(FourCC::new(b"YUYV"), 1024, 480),
stepwise(FourCC::new(b"RGGB"), (4, 4096, 4), (1, 480, 1)),
])
}
fn db_basic() -> ConfigsDatabase {
db_with(vec![
discrete(FourCC::new(b"YUYV"), 640, 480),
discrete(FourCC::new(b"RGBA"), 640, 480),
discrete(FourCC::new(b"YUYV"), 1024, 480),
stepwise(FourCC::new(b"RGGB"), (640, 1280, 640), (480, 980, 480)),
])
}
#[test]
fn query_resolutions() {
let db = db_basic();
let solutions = db.query_str(
"config_path_by_name(_, videoformat(_, W, H), Config).",
).unwrap();
let uniques = HashSet::<Dimensions>::from_iter(
solutions.iter().map(|solution| (
get_int(solution.get_term("W").unwrap()).unwrap(),
get_int(solution.get_term("H").unwrap()).unwrap(),
))
);
assert_eq!(
uniques,
HashSet::from([
(640, 480),
(1024, 480),
(640, 960),
(1280, 480),
(1280, 960),
]),
)
}
#[test]
fn query_resolutions_by_format() {
let db = db();
let solutions = db.query_str(
"config_path_by_name(_, videoformat(fourcc_YUYV, W, H), Config).",
).unwrap();
let uniques = HashSet::<Dimensions>::from_iter(
solutions.iter().map(|solution| (
get_int(solution.get_term("W").unwrap()).unwrap(),
get_int(solution.get_term("H").unwrap()).unwrap(),
))
);
assert_eq!(
uniques,
HashSet::from([
(640, 480),
(1024, 480),
]),
)
}
#[test]
fn query_by_format() {
let db = db();
let request = ConfigRequest {
fourcc: Some(FourCC::new(b"RGBA")),
..ConfigRequest::default()
};
let solutions = dbg!(db.query_configs(request));
let configs = solutions.iter()
.map(|deviceconfig| deviceconfig.as_config().clone())
.collect::<Vec<_>>();
assert_eq!(
configs,
vec![
Config {
width: 640,
height: 480,
fourcc: FourCC::new(b"RGBA"),
},
],
);
}
#[test]
fn query_by_width() {
let db = db_basic();
let request = ConfigRequest {
width: Some(640),
..ConfigRequest::default()
};
let solutions = dbg!(db.query_configs(request));
let configs = solutions.iter()
.map(|deviceconfig| deviceconfig.as_config().clone())
.collect::<HashSet<_>>();
assert_eq!(
configs,
HashSet::from_iter(vec![
Config {
width: 640,
height: 480,
fourcc: FourCC::new(b"YUYV"),
},
Config {
width: 640,
height: 480,
fourcc: FourCC::new(b"RGBA"),
},
Config {
width: 640,
height: 480,
fourcc: FourCC::new(b"RGGB"),
},
Config {
width: 640,
height: 960,
fourcc: FourCC::new(b"RGGB"),
},
]),
);
}
#[test]
fn query_by_resolution() {
let db = db();
let request = ConfigRequest {
width: Some(640),
height: Some(480),
..ConfigRequest::default()
};
let solutions = dbg!(db.query_configs(request));
let configs = solutions.iter()
.map(|deviceconfig| deviceconfig.as_config().clone())
.collect::<HashSet<_>>();
assert_eq!(
configs,
HashSet::from_iter(vec![
Config {
width: 640,
height: 480,
fourcc: FourCC::new(b"YUYV"),
},
Config {
width: 640,
height: 480,
fourcc: FourCC::new(b"RGBA"),
},
Config {
width: 640,
height: 480,
fourcc: FourCC::new(b"RGGB"),
},
]),
);
}
#[test]
fn query_stepwise() {
let db = db();
let request = ConfigRequest {
fourcc: Some(FourCC::new(b"RGGB")),
width: Some(640),
height: Some(240),
};
let solutions = dbg!(db.query_configs(request));
let configs = solutions.iter()
.map(|deviceconfig| deviceconfig.as_config().clone())
.collect::<HashSet<_>>();
assert_eq!(
configs,
HashSet::from_iter(vec![
Config {
width: 640,
height: 240,
fourcc: FourCC::new(b"RGGB"),
},
]),
);
}
#[test]
fn query_stepwise_range() {
let db = db_basic();
let request = ConfigRequest {
fourcc: Some(FourCC::new(b"RGGB")),
width: Some(1280),
..Default::default()
};
let solutions = dbg!(db.query_configs(request));
let configs = solutions.iter()
.map(|deviceconfig| deviceconfig.as_config().clone())
.collect::<HashSet<_>>();
assert_eq!(
configs,
HashSet::from_iter(vec![
Config {
width: 1280,
height: 480,
fourcc: FourCC::new(b"RGGB"),
},
Config {
width: 1280,
height: 960,
fourcc: FourCC::new(b"RGGB"),
},
]),
);
}
#[test]
fn query_stepwise_range_pipeline() {
let db = db_basic();
let request = ConfigRequest {
fourcc: Some(FourCC::new(b"RGGB")),
width: Some(1280),
height: Some(480),
..Default::default()
};
let solutions = dbg!(db.query_configs(request));
let configs = solutions.iter()
.map(|deviceconfig| deviceconfig.config().clone())
.collect::<HashSet<_>>();
assert_eq!(
configs,
HashSet::from_iter(vec![
PipelineState {
pads: vec![
PadState {
id: EntityId(111),
pad_idx: 0,
width: 1280,
height: 480,
mbus: MediaBusFmt::FIXED,
},
],
video_entity: VideoState {
id: EntityId(222),
pad_idx: 0,
width: 1280,
height: 480,
fourcc: FourCC::new(b"RGGB"),
},
},
]),
);
}
}