Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add speed setter and getter #2

Merged
merged 4 commits into from
Jan 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,11 @@ automatically at build time.
## Testing

Test assumes there is a running AAOS VM. Again, make sure you also have ADB installed.
Also, tests may interfere when run in parallel. To avoid false negatives, make
sure you force a single thread.

```shell
$ ADB_PATH=/path/to/adb/bin cargo test
$ ADB_PATH=/path/to/adb/bin cargo test --lib -- --test-threads=1
aesteve-rh marked this conversation as resolved.
Show resolved Hide resolved
```

## Regenerating vhal constants
Expand Down
205 changes: 198 additions & 7 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,19 @@ const REMOTE_PORT: u16 = 33452;

pub type Result<T> = std::result::Result<T, VhalError>;

pub trait GetPropertyValue {
fn get_value(&self) -> Result<VehiclePropertyValue>;
}

pub trait CheckResponse {
fn is_valid(&self, exp_type: VehicleHalProto::MsgType) -> Result<()>;
}

pub trait ExpectType {
fn expect_i32(&self) -> Result<i32>;
fn expect_f32(&self) -> Result<f32>;
}

#[derive(Debug, ThisError)]
pub enum VhalError {
#[error("adb command failed: {0}")]
Expand All @@ -39,6 +52,16 @@ pub enum VhalError {
ReceiveMessageError(std::io::Error),
#[error("Unexpected message length, expected {0}, found {1}")]
ReceiveMessageLengthError(usize, usize),
#[error("Unexpected message status: {0}")]
ReceiveMessageStatusError(usize),
#[error("Unexpected message type: {0}")]
ReceiveMessageTypeError(usize),
#[error("Unexpected message value type")]
ReceiveMessageValueTypeError,
#[error("Received message invalid value")]
ReceiveMessageValueError,
aesteve-rh marked this conversation as resolved.
Show resolved Hide resolved
#[error("Received too many message values, expected 1, found {0}")]
ReceiveMessageTooManyValuesError(usize),
#[error("Invalid property ID: {0}")]
PropertyError(i32),
#[error("Mismatched property type received")]
Expand Down Expand Up @@ -66,17 +89,112 @@ impl VehiclePropertyValue {
}
}

fn check_valid_type(&self, vhal_prop_type: &VehiclePropertyType) -> bool {
fn from_prop(
prop_type: &VehiclePropertyType,
prop_value: &VehicleHalProto::VehiclePropValue,
) -> Result<Self> {
Ok(match *prop_type {
VehiclePropertyType::STRING => Self::String(
prop_value
.string_value
.as_ref()
.ok_or(VhalError::PropertyTypeError)?
.to_owned(),
),
VehiclePropertyType::BYTES => Self::Bytes(
prop_value
.bytes_value
.as_ref()
.ok_or(VhalError::PropertyTypeError)?
.to_vec(),
),
VehiclePropertyType::BOOLEAN => Self::Int32(
aesteve-rh marked this conversation as resolved.
Show resolved Hide resolved
*prop_value
.int32_values
.first()
.ok_or(VhalError::PropertyTypeError)?,
),
VehiclePropertyType::INT32 => Self::Int32(
*prop_value
.int32_values
.first()
.ok_or(VhalError::PropertyTypeError)?,
),
VehiclePropertyType::INT64 => Self::Int64(
*prop_value
.int64_values
.first()
.ok_or(VhalError::PropertyTypeError)?,
),
VehiclePropertyType::FLOAT => Self::Float(
*prop_value
.float_values
.first()
.ok_or(VhalError::PropertyTypeError)?,
),
_ => return Err(VhalError::ReceiveMessageValueTypeError),
})
}

fn check_valid_type(&self, prop_type: &VehiclePropertyType) -> bool {
match self {
Self::String(_) => vhal_prop_type.is_string(),
Self::Int32(_) => vhal_prop_type.is_int32(),
Self::Int64(_) => vhal_prop_type.is_int64(),
Self::Float(_) => vhal_prop_type.is_float(),
Self::Bytes(_) => vhal_prop_type.is_bytes(),
Self::String(_) => prop_type.is_string(),
Self::Int32(_) => prop_type.is_int32(),
Self::Int64(_) => prop_type.is_int64(),
Self::Float(_) => prop_type.is_float(),
Self::Bytes(_) => prop_type.is_bytes(),
}
}
}

impl GetPropertyValue for EmulatorMessage {
fn get_value(&self) -> Result<VehiclePropertyValue> {
if self.value.len() != 1 {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it can't be zero, maybe better change to >1.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, the only valid value is 1. With >1 it would allow empty vectors.

Copy link

@barpavel barpavel Jan 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, you misunderstood me.
I meant the following:
!=1 means either 0 or more than 1 (2, 3, ...).
If zero values is possible, then returning ReceiveMessageTooManyValuesError is confusing.
If zero of values is not possible, then changing !=1 into >1 is the same, but more readable.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But never mind, this is not that important, just explaining myself :)

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah! gotcha. Well, at least the size of the values vector is printed with the error :D

return Err(VhalError::ReceiveMessageTooManyValuesError(
self.value.len(),
));
}

let value = self.value.first().unwrap();
let val_type = VehiclePropertyType::try_from(match value.value_type {
Some(val) => val,
None => return Err(VhalError::ReceiveMessageValueError),
})
.map_err(|_| VhalError::ReceiveMessageValueTypeError)?;

VehiclePropertyValue::from_prop(&val_type, value)
}
}

impl CheckResponse for EmulatorMessage {
fn is_valid(&self, exp_type: VehicleHalProto::MsgType) -> Result<()> {
if self.status() != VehicleHalProto::Status::RESULT_OK {
return Err(VhalError::ReceiveMessageStatusError(self.status() as usize));
}
if self.msg_type() != exp_type {
return Err(VhalError::ReceiveMessageTypeError(self.msg_type() as usize));
}

Ok(())
}
}

impl ExpectType for EmulatorMessage {
fn expect_i32(&self) -> Result<i32> {
Ok(match self.get_value()? {
VehiclePropertyValue::Int32(value) => value,
_ => return Err(VhalError::ReceiveMessageValueTypeError),
})
}

fn expect_f32(&self) -> Result<f32> {
Ok(match self.get_value()? {
VehiclePropertyValue::Float(value) => value,
_ => return Err(VhalError::ReceiveMessageValueTypeError),
})
}
}

pub fn adb_port_forwarding() -> Result<u16> {
use std::str;
let adb_path = match env::var("ADB_PATH") {
Expand Down Expand Up @@ -192,11 +310,56 @@ impl Vhal {
Ok(())
}

pub fn get_property(&self, prop: VehicleProperty, area_id: i32) -> Result<()> {
let mut cmd = EmulatorMessage::new();
let mut vhal_prop_get = VehicleHalProto::VehiclePropGet::new();
cmd.set_msg_type(VehicleHalProto::MsgType::GET_PROPERTY_CMD);
vhal_prop_get.set_prop(prop as i32);
vhal_prop_get.set_area_id(area_id);
cmd.prop.push(vhal_prop_get);
self.send_cmd(cmd)?;

Ok(())
}

pub fn set_gear_selection(&self, gear: c::VehicleGear) -> Result<()> {
let value = VehiclePropertyValue::Int32(gear as i32);
self.set_property(VehicleProperty::GEAR_SELECTION, value, 0, None)
}

pub fn get_gear_selection(&self) -> Result<c::VehicleGear> {
self.get_property(VehicleProperty::GEAR_SELECTION, 0)?;
let resp = self.recv_cmd()?;
resp.is_valid(VehicleHalProto::MsgType::GET_PROPERTY_RESP)?;

Ok(c::VehicleGear::try_from(resp.expect_i32()?)
.map_err(|_| VhalError::ReceiveMessageValueError)?)
}

pub fn set_vehicle_speed(&self, speed: f32) -> Result<()> {
let value = VehiclePropertyValue::Float(speed);
self.set_property(VehicleProperty::PERF_VEHICLE_SPEED, value, 0, None)
}

pub fn get_vehicle_speed(&self) -> Result<f32> {
self.get_property(VehicleProperty::PERF_VEHICLE_SPEED, 0)?;
let resp = self.recv_cmd()?;
resp.is_valid(VehicleHalProto::MsgType::GET_PROPERTY_RESP)?;
resp.expect_f32()
}

pub fn set_vehicle_display_speed(&self, speed: f32) -> Result<()> {
let value = VehiclePropertyValue::Float(speed);
self.set_property(VehicleProperty::PERF_VEHICLE_SPEED_DISPLAY, value, 0, None)
}

pub fn get_vehicle_display_speed(&self) -> Result<f32> {
self.get_property(VehicleProperty::PERF_VEHICLE_SPEED_DISPLAY, 0)?;
let resp = self.recv_cmd()?;
resp.is_valid(VehicleHalProto::MsgType::GET_PROPERTY_RESP)?;
resp.expect_f32()
}

fn send_cmd(&self, cmd: EmulatorMessage) -> Result<()> {
debug!("Sending command: {:?}", cmd);
let msg_bytes = cmd.write_to_bytes().expect("msg");
Expand Down Expand Up @@ -284,11 +447,39 @@ mod tests {
#[case::park(c::VehicleGear::GEAR_PARK)]
#[case::drive(c::VehicleGear::GEAR_DRIVE)]
#[case::reverse(c::VehicleGear::GEAR_REVERSE)]
fn set_gear_test(local_port: u16, #[case] gear: c::VehicleGear) {
fn gear_selection_test(local_port: u16, #[case] gear: c::VehicleGear) {
let vhal = Vhal::new(local_port).unwrap();
vhal.set_gear_selection(gear).unwrap();
assert!(vhal
.recv_cmd()
.is_ok_and(|cmd| cmd.has_status() && cmd.status() == Status::RESULT_OK));
assert!(vhal
.get_gear_selection()
.is_ok_and(|rcv_gear| rcv_gear == gear));
}

#[rstest]
#[case::stopped(0.0)]
#[case::slow(30.0)]
#[case::city(50.0)]
#[case::road(80.0)]
#[case::highway(120.0)]
#[case::reverse(-10.0)]
fn speed_test(local_port: u16, #[case] speed: f32) {
let vhal = Vhal::new(local_port).unwrap();
vhal.set_vehicle_speed(speed).unwrap();
assert!(vhal
.recv_cmd()
.is_ok_and(|cmd| cmd.has_status() && cmd.status() == Status::RESULT_OK));
assert!(vhal
.get_vehicle_speed()
.is_ok_and(|rcv_speed| rcv_speed == speed));
vhal.set_vehicle_display_speed(speed).unwrap();
assert!(vhal
.recv_cmd()
.is_ok_and(|cmd| cmd.has_status() && cmd.status() == Status::RESULT_OK));
assert!(vhal
.get_vehicle_display_speed()
.is_ok_and(|rcv_speed| rcv_speed == speed));
}
}