Add more devices
This commit is contained in:
@@ -5,6 +5,7 @@ rustflags = [
|
||||
"-Zfmt-debug=none",
|
||||
"-Zunstable-options",
|
||||
"-Cpanic=immediate-abort",
|
||||
"-Zpanic_abort_tests",
|
||||
]
|
||||
|
||||
[unstable]
|
||||
|
||||
13
README.md
13
README.md
@@ -58,20 +58,23 @@ For common peripherals you can rely on typed wrappers. The redstone helper
|
||||
mirrors the behaviour of `redstone.lua` and performs basic clamping/parsing:
|
||||
|
||||
```rust
|
||||
use oc2r_rust::devices::redstone::{Redstone, Side};
|
||||
use oc2r_rust::{DeviceBus, Result, DEFAULT_DEVICE_PATH};
|
||||
use oc2r_rust::{DeviceBus, Redstone, Result, Side, DEFAULT_DEVICE_PATH};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let mut bus = DeviceBus::connect(DEFAULT_DEVICE_PATH)?;
|
||||
let mut redstone = Redstone::attach(&mut bus)?
|
||||
.expect("no redstone interface attached");
|
||||
|
||||
redstone.set_output_state(Side::East, true)?;
|
||||
println!("in: {}", redstone.input(Side::East)?);
|
||||
Ok(())
|
||||
redstone.set_output_state(Side::East, true)?;
|
||||
println!("in: {}", redstone.input(Side::East)?);
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
Available wrappers: `Redstone`, `EnergyStorage`, `FluidHandler`, `ItemHandler`,
|
||||
`Cpu`, `SoundCard`, `BlockOperations`, `InventoryOperations`, and
|
||||
`FileImportExport`.
|
||||
|
||||
### Building the examples
|
||||
|
||||
Two runnable examples live in `examples/`:
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Build with: cross build --release --example redstone
|
||||
|
||||
use oc2r_rust::devices::redstone::{Redstone, Side};
|
||||
use oc2r_rust::{DEFAULT_DEVICE_PATH, DeviceBus, Result};
|
||||
use oc2r_rust::{DEFAULT_DEVICE_PATH, DeviceBus, Redstone, Result, Side};
|
||||
use std::env;
|
||||
use std::str::FromStr;
|
||||
|
||||
|
||||
56
src/devices/block_operations.rs
Normal file
56
src/devices/block_operations.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
use crate::Result;
|
||||
use crate::value::JsonValueExt;
|
||||
|
||||
crate::devices::define_wrapper!(
|
||||
BlockOperations,
|
||||
"block_operations",
|
||||
"Wrapper for the block operations robot module."
|
||||
);
|
||||
|
||||
impl<'bus> BlockOperations<'bus> {
|
||||
pub fn excavate(&mut self) -> Result<bool> {
|
||||
self.device.call("excavate", ())?.into_bool("excavate")
|
||||
}
|
||||
|
||||
pub fn excavate_on(&mut self, side: RobotSide) -> Result<bool> {
|
||||
self.device
|
||||
.call("excavate", (side.as_str(),))?
|
||||
.into_bool("excavate")
|
||||
}
|
||||
|
||||
pub fn place(&mut self) -> Result<bool> {
|
||||
self.device.call("place", ())?.into_bool("place")
|
||||
}
|
||||
|
||||
pub fn place_on(&mut self, side: RobotSide) -> Result<bool> {
|
||||
self.device
|
||||
.call("place", (side.as_str(),))?
|
||||
.into_bool("place")
|
||||
}
|
||||
|
||||
pub fn durability(&mut self) -> Result<i64> {
|
||||
self.device.call("durability", ())?.into_i64("durability")
|
||||
}
|
||||
|
||||
pub fn repair(&mut self) -> Result<bool> {
|
||||
self.device.call("repair", ())?.into_bool("repair")
|
||||
}
|
||||
}
|
||||
|
||||
/// Robot operation sides recognised by OC2R.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum RobotSide {
|
||||
Front,
|
||||
Up,
|
||||
Down,
|
||||
}
|
||||
|
||||
impl RobotSide {
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
RobotSide::Front => "front",
|
||||
RobotSide::Up => "up",
|
||||
RobotSide::Down => "down",
|
||||
}
|
||||
}
|
||||
}
|
||||
12
src/devices/cpu.rs
Normal file
12
src/devices/cpu.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
use crate::Result;
|
||||
use crate::value::JsonValueExt;
|
||||
|
||||
crate::devices::define_wrapper!(Cpu, "cpu", "Wrapper exposing CPU metadata.");
|
||||
|
||||
impl<'bus> Cpu<'bus> {
|
||||
pub fn frequency(&mut self) -> Result<i64> {
|
||||
self.device
|
||||
.call("getFrequency", ())?
|
||||
.into_i64("getFrequency")
|
||||
}
|
||||
}
|
||||
34
src/devices/energy_storage.rs
Normal file
34
src/devices/energy_storage.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use crate::Result;
|
||||
use crate::value::JsonValueExt;
|
||||
|
||||
crate::devices::define_wrapper!(
|
||||
EnergyStorage,
|
||||
"energy_storage",
|
||||
"Wrapper for OC2R energy storage devices."
|
||||
);
|
||||
|
||||
impl<'bus> EnergyStorage<'bus> {
|
||||
pub fn energy_stored(&mut self) -> Result<i64> {
|
||||
self.device
|
||||
.call("getEnergyStored", ())?
|
||||
.into_i64("getEnergyStored")
|
||||
}
|
||||
|
||||
pub fn max_energy_stored(&mut self) -> Result<i64> {
|
||||
self.device
|
||||
.call("getMaxEnergyStored", ())?
|
||||
.into_i64("getMaxEnergyStored")
|
||||
}
|
||||
|
||||
pub fn can_extract(&mut self) -> Result<bool> {
|
||||
self.device
|
||||
.call("canExtractEnergy", ())?
|
||||
.into_bool("canExtractEnergy")
|
||||
}
|
||||
|
||||
pub fn can_receive(&mut self) -> Result<bool> {
|
||||
self.device
|
||||
.call("canReceiveEnergy", ())?
|
||||
.into_bool("canReceiveEnergy")
|
||||
}
|
||||
}
|
||||
87
src/devices/file_import_export.rs
Normal file
87
src/devices/file_import_export.rs
Normal file
@@ -0,0 +1,87 @@
|
||||
use crate::value::JsonValueExt;
|
||||
use crate::{Error, JsonValue, Result};
|
||||
use miniserde::json::{Array, Number};
|
||||
|
||||
crate::devices::define_wrapper!(
|
||||
FileImportExport,
|
||||
"file_import_export",
|
||||
"Wrapper for the file import/export card."
|
||||
);
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ImportedFileInfo {
|
||||
pub name: Option<String>,
|
||||
pub size: u64,
|
||||
}
|
||||
|
||||
impl<'bus> FileImportExport<'bus> {
|
||||
pub fn reset(&mut self) -> Result<()> {
|
||||
self.device.call("reset", ()).map(|_| ())
|
||||
}
|
||||
|
||||
pub fn begin_export(&mut self, name: &str) -> Result<()> {
|
||||
self.device.call("beginExportFile", (name,)).map(|_| ())
|
||||
}
|
||||
|
||||
pub fn write_export_chunk(&mut self, chunk: &[u8]) -> Result<()> {
|
||||
let payload = JsonValue::Array(
|
||||
chunk
|
||||
.iter()
|
||||
.copied()
|
||||
.map(|byte| JsonValue::Number(Number::U64(byte as u64)))
|
||||
.collect::<Array>(),
|
||||
);
|
||||
self.device.call("writeExportFile", (payload,)).map(|_| ())
|
||||
}
|
||||
|
||||
pub fn finish_export(&mut self) -> Result<()> {
|
||||
self.device.call("finishExportFile", ()).map(|_| ())
|
||||
}
|
||||
|
||||
pub fn request_import(&mut self) -> Result<bool> {
|
||||
self.device
|
||||
.call("requestImportFile", ())?
|
||||
.into_bool("requestImportFile")
|
||||
}
|
||||
|
||||
pub fn begin_import(&mut self) -> Result<Option<ImportedFileInfo>> {
|
||||
match self.device.call("beginImportFile", ())? {
|
||||
JsonValue::Null => Ok(None),
|
||||
value => {
|
||||
let mut object = value.into_object("beginImportFile")?;
|
||||
let name = match object.remove("name") {
|
||||
Some(JsonValue::Null) | None => None,
|
||||
Some(other) => Some(other.into_string("beginImportFile.name")?),
|
||||
};
|
||||
let size_value = object
|
||||
.remove("size")
|
||||
.ok_or_else(|| Error::Protocol("beginImportFile missing size".into()))?;
|
||||
let size = size_value.into_u64("beginImportFile.size")?;
|
||||
Ok(Some(ImportedFileInfo { name, size }))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_import_chunk(&mut self) -> Result<Option<Vec<u8>>> {
|
||||
match self.device.call("readImportFile", ())? {
|
||||
JsonValue::Null => Ok(None),
|
||||
value => {
|
||||
let bytes = value
|
||||
.into_array("readImportFile")?
|
||||
.into_iter()
|
||||
.map(|item| {
|
||||
let value = item.into_u64("readImportFile byte")?;
|
||||
if value > 255 {
|
||||
Err(Error::Protocol(format!(
|
||||
"readImportFile returned byte value {value} > 255"
|
||||
)))
|
||||
} else {
|
||||
Ok(value as u8)
|
||||
}
|
||||
})
|
||||
.collect::<Result<Vec<u8>>>()?;
|
||||
Ok(Some(bytes))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
29
src/devices/fluid_handler.rs
Normal file
29
src/devices/fluid_handler.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use crate::value::JsonValueExt;
|
||||
use crate::{JsonValue, Result};
|
||||
|
||||
crate::devices::define_wrapper!(
|
||||
FluidHandler,
|
||||
"fluid_handler",
|
||||
"Wrapper for Forge fluid handler devices."
|
||||
);
|
||||
|
||||
impl<'bus> FluidHandler<'bus> {
|
||||
pub fn tank_count(&mut self) -> Result<i64> {
|
||||
self.device
|
||||
.call("getFluidTanks", ())?
|
||||
.into_i64("getFluidTanks")
|
||||
}
|
||||
|
||||
pub fn fluid_in_tank(&mut self, tank: usize) -> Result<Option<JsonValue>> {
|
||||
match self.device.call("getFluidInTank", (tank,))? {
|
||||
JsonValue::Null => Ok(None),
|
||||
other => Ok(Some(other)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tank_capacity(&mut self, tank: usize) -> Result<i64> {
|
||||
self.device
|
||||
.call("getFluidTankCapacity", (tank,))?
|
||||
.into_i64("getFluidTankCapacity")
|
||||
}
|
||||
}
|
||||
100
src/devices/inventory_operations.rs
Normal file
100
src/devices/inventory_operations.rs
Normal file
@@ -0,0 +1,100 @@
|
||||
use crate::Result;
|
||||
use crate::devices::RobotSide;
|
||||
use crate::value::JsonValueExt;
|
||||
|
||||
crate::devices::define_wrapper!(
|
||||
InventoryOperations,
|
||||
"inventory_operations",
|
||||
"Wrapper for the inventory operations robot module."
|
||||
);
|
||||
|
||||
impl<'bus> InventoryOperations<'bus> {
|
||||
pub fn move_items(&mut self, from_slot: usize, into_slot: usize, count: u32) -> Result<()> {
|
||||
self.device
|
||||
.call("move", (from_slot, into_slot, count))
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
pub fn drop(&mut self, count: u32) -> Result<i64> {
|
||||
self.drop_internal(count, None)
|
||||
}
|
||||
|
||||
pub fn drop_on(&mut self, count: u32, side: RobotSide) -> Result<i64> {
|
||||
self.drop_internal(count, Some(side))
|
||||
}
|
||||
|
||||
pub fn drop_into(&mut self, into_slot: usize, count: u32) -> Result<i64> {
|
||||
self.drop_into_internal(into_slot, count, None)
|
||||
}
|
||||
|
||||
pub fn drop_into_on(&mut self, into_slot: usize, count: u32, side: RobotSide) -> Result<i64> {
|
||||
self.drop_into_internal(into_slot, count, Some(side))
|
||||
}
|
||||
|
||||
pub fn take(&mut self, count: u32) -> Result<i64> {
|
||||
self.take_internal(count, None)
|
||||
}
|
||||
|
||||
pub fn take_on(&mut self, count: u32, side: RobotSide) -> Result<i64> {
|
||||
self.take_internal(count, Some(side))
|
||||
}
|
||||
|
||||
pub fn take_from_slot(&mut self, from_slot: usize, count: u32) -> Result<i64> {
|
||||
self.take_from_internal(from_slot, count, None)
|
||||
}
|
||||
|
||||
pub fn take_from_slot_on(
|
||||
&mut self,
|
||||
from_slot: usize,
|
||||
count: u32,
|
||||
side: RobotSide,
|
||||
) -> Result<i64> {
|
||||
self.take_from_internal(from_slot, count, Some(side))
|
||||
}
|
||||
|
||||
fn drop_internal(&mut self, count: u32, side: Option<RobotSide>) -> Result<i64> {
|
||||
let value = match side {
|
||||
Some(side) => self.device.call("drop", (count, side.as_str()))?,
|
||||
None => self.device.call("drop", (count,))?,
|
||||
};
|
||||
value.into_i64("drop")
|
||||
}
|
||||
|
||||
fn drop_into_internal(
|
||||
&mut self,
|
||||
into_slot: usize,
|
||||
count: u32,
|
||||
side: Option<RobotSide>,
|
||||
) -> Result<i64> {
|
||||
let value = match side {
|
||||
Some(side) => self
|
||||
.device
|
||||
.call("dropInto", (into_slot, count, side.as_str()))?,
|
||||
None => self.device.call("dropInto", (into_slot, count))?,
|
||||
};
|
||||
value.into_i64("dropInto")
|
||||
}
|
||||
|
||||
fn take_internal(&mut self, count: u32, side: Option<RobotSide>) -> Result<i64> {
|
||||
let value = match side {
|
||||
Some(side) => self.device.call("take", (count, side.as_str()))?,
|
||||
None => self.device.call("take", (count,))?,
|
||||
};
|
||||
value.into_i64("take")
|
||||
}
|
||||
|
||||
fn take_from_internal(
|
||||
&mut self,
|
||||
from_slot: usize,
|
||||
count: u32,
|
||||
side: Option<RobotSide>,
|
||||
) -> Result<i64> {
|
||||
let value = match side {
|
||||
Some(side) => self
|
||||
.device
|
||||
.call("takeFrom", (from_slot, count, side.as_str()))?,
|
||||
None => self.device.call("takeFrom", (from_slot, count))?,
|
||||
};
|
||||
value.into_i64("takeFrom")
|
||||
}
|
||||
}
|
||||
29
src/devices/item_handler.rs
Normal file
29
src/devices/item_handler.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use crate::value::JsonValueExt;
|
||||
use crate::{JsonValue, Result};
|
||||
|
||||
crate::devices::define_wrapper!(
|
||||
ItemHandler,
|
||||
"item_handler",
|
||||
"Wrapper for Forge item handler devices."
|
||||
);
|
||||
|
||||
impl<'bus> ItemHandler<'bus> {
|
||||
pub fn slot_count(&mut self) -> Result<i64> {
|
||||
self.device
|
||||
.call("getItemSlotCount", ())?
|
||||
.into_i64("getItemSlotCount")
|
||||
}
|
||||
|
||||
pub fn stack_in_slot(&mut self, slot: usize) -> Result<Option<JsonValue>> {
|
||||
match self.device.call("getItemStackInSlot", (slot,))? {
|
||||
JsonValue::Null => Ok(None),
|
||||
other => Ok(Some(other)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn slot_limit(&mut self, slot: usize) -> Result<i64> {
|
||||
self.device
|
||||
.call("getItemSlotLimit", (slot,))?
|
||||
.into_i64("getItemSlotLimit")
|
||||
}
|
||||
}
|
||||
@@ -1 +1,65 @@
|
||||
macro_rules! define_wrapper {
|
||||
($name:ident, $type_name:literal, $doc:literal) => {
|
||||
#[doc = $doc]
|
||||
pub struct $name<'bus> {
|
||||
pub(crate) device: crate::Device<'bus>,
|
||||
}
|
||||
|
||||
impl<'bus> $name<'bus> {
|
||||
pub const TYPE_NAME: &'static str = $type_name;
|
||||
|
||||
pub fn attach(bus: &'bus mut crate::DeviceBus) -> crate::Result<Option<Self>> {
|
||||
crate::devices::attach_device(bus, Self::TYPE_NAME)
|
||||
.map(|opt| opt.map(Self::from_device))
|
||||
}
|
||||
|
||||
pub fn from_device(device: crate::Device<'bus>) -> Self {
|
||||
Self { device }
|
||||
}
|
||||
|
||||
pub fn info(&self) -> &crate::DeviceEntry {
|
||||
self.device.info()
|
||||
}
|
||||
|
||||
pub fn as_device(&mut self) -> &mut crate::Device<'bus> {
|
||||
&mut self.device
|
||||
}
|
||||
|
||||
pub fn into_device(self) -> crate::Device<'bus> {
|
||||
self.device
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) use define_wrapper;
|
||||
|
||||
pub(crate) fn attach_device<'bus>(
|
||||
bus: &'bus mut crate::DeviceBus,
|
||||
type_name: &str,
|
||||
) -> crate::Result<Option<crate::Device<'bus>>> {
|
||||
match bus.find(type_name)? {
|
||||
Some(entry) => bus.device(&entry.device_id),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub mod block_operations;
|
||||
pub mod cpu;
|
||||
pub mod energy_storage;
|
||||
pub mod file_import_export;
|
||||
pub mod fluid_handler;
|
||||
pub mod inventory_operations;
|
||||
pub mod item_handler;
|
||||
pub mod redstone;
|
||||
pub mod sound;
|
||||
|
||||
pub use block_operations::{BlockOperations, RobotSide};
|
||||
pub use cpu::Cpu;
|
||||
pub use energy_storage::EnergyStorage;
|
||||
pub use file_import_export::{FileImportExport, ImportedFileInfo};
|
||||
pub use fluid_handler::FluidHandler;
|
||||
pub use inventory_operations::InventoryOperations;
|
||||
pub use item_handler::ItemHandler;
|
||||
pub use redstone::{ParseSideError, Redstone, Side};
|
||||
pub use sound::SoundCard;
|
||||
|
||||
@@ -1,59 +1,35 @@
|
||||
use crate::bus::Device;
|
||||
use crate::{DeviceBus, DeviceEntry, Error, JsonValue, Result};
|
||||
use miniserde::json::Number;
|
||||
use crate::Result;
|
||||
use crate::value::JsonValueExt;
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
/// Typed wrapper around the HLAPI redstone interface.
|
||||
pub struct Redstone<'bus> {
|
||||
device: Device<'bus>,
|
||||
}
|
||||
crate::devices::define_wrapper!(
|
||||
Redstone,
|
||||
"redstone",
|
||||
"Typed wrapper around the HLAPI redstone interface."
|
||||
);
|
||||
|
||||
impl<'bus> Redstone<'bus> {
|
||||
pub const TYPE_NAME: &'static str = "redstone";
|
||||
|
||||
/// Attempt to find and wrap the first redstone-compatible device on the bus.
|
||||
pub fn attach(bus: &'bus mut DeviceBus) -> Result<Option<Self>> {
|
||||
let Some(entry) = bus.find(Self::TYPE_NAME)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
Ok(bus.device(&entry.device_id)?.map(|device| Self { device }))
|
||||
}
|
||||
|
||||
/// Create a typed wrapper from an existing [`Device`].
|
||||
pub fn from_device(device: Device<'bus>) -> Self {
|
||||
Self { device }
|
||||
}
|
||||
|
||||
/// Access the underlying device entry metadata.
|
||||
pub fn info(&self) -> &DeviceEntry {
|
||||
self.device.info()
|
||||
}
|
||||
|
||||
/// Borrow the underlying dynamic device handle.
|
||||
pub fn as_device(&mut self) -> &mut Device<'bus> {
|
||||
&mut self.device
|
||||
}
|
||||
|
||||
/// Read the redstone input level for `side`.
|
||||
pub fn input(&mut self, side: Side) -> Result<f64> {
|
||||
let value = self.device.call("getRedstoneInput", (side.as_str(),))?;
|
||||
expect_number(value, "getRedstoneInput")
|
||||
self.device
|
||||
.call("getRedstoneInput", (side.as_str(),))?
|
||||
.into_f64("getRedstoneInput")
|
||||
}
|
||||
|
||||
/// Read the currently configured output level for `side`.
|
||||
pub fn output(&mut self, side: Side) -> Result<f64> {
|
||||
let value = self.device.call("getRedstoneOutput", (side.as_str(),))?;
|
||||
expect_number(value, "getRedstoneOutput")
|
||||
self.device
|
||||
.call("getRedstoneOutput", (side.as_str(),))?
|
||||
.into_f64("getRedstoneOutput")
|
||||
}
|
||||
|
||||
/// Set the output strength on `side` (0-15).
|
||||
pub fn set_output_level(&mut self, side: Side, level: u8) -> Result<()> {
|
||||
let clamped = level.min(15) as u64;
|
||||
let _ = self
|
||||
.device
|
||||
.call("setRedstoneOutput", (side.as_str(), clamped))?;
|
||||
Ok(())
|
||||
let clamped = level.min(15);
|
||||
self.device
|
||||
.call("setRedstoneOutput", (side.as_str(), clamped))
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
/// Convenience for enabling (`true`) or disabling (`false`) an output.
|
||||
@@ -62,17 +38,6 @@ impl<'bus> Redstone<'bus> {
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_number(value: JsonValue, context: &str) -> Result<f64> {
|
||||
match value {
|
||||
JsonValue::Number(Number::F64(v)) => Ok(v),
|
||||
JsonValue::Number(Number::I64(v)) => Ok(v as f64),
|
||||
JsonValue::Number(Number::U64(v)) => Ok(v as f64),
|
||||
other => Err(Error::Protocol(format!(
|
||||
"expected numeric response from {context}, got {other:?}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Redstone interface sides used by OC2R.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Side {
|
||||
|
||||
38
src/devices/sound.rs
Normal file
38
src/devices/sound.rs
Normal file
@@ -0,0 +1,38 @@
|
||||
use crate::Result;
|
||||
use crate::value::JsonValueExt;
|
||||
|
||||
crate::devices::define_wrapper!(
|
||||
SoundCard,
|
||||
"sound",
|
||||
"Wrapper around the OC2R sound card HLAPI."
|
||||
);
|
||||
|
||||
impl<'bus> SoundCard<'bus> {
|
||||
pub fn play(&mut self, name: &str) -> Result<()> {
|
||||
self.device.call("playSound", (name,)).map(|_| ())
|
||||
}
|
||||
|
||||
pub fn play_with_volume(&mut self, name: &str, volume: f32) -> Result<()> {
|
||||
self.device.call("playSound", (name, volume)).map(|_| ())
|
||||
}
|
||||
|
||||
pub fn play_with_volume_and_pitch(
|
||||
&mut self,
|
||||
name: &str,
|
||||
volume: f32,
|
||||
pitch: f32,
|
||||
) -> Result<()> {
|
||||
self.device
|
||||
.call("playSound", (name, volume, pitch))
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
pub fn find(&mut self, query: &str) -> Result<Vec<String>> {
|
||||
self.device
|
||||
.call("findSound", (query,))?
|
||||
.into_array("findSound")?
|
||||
.into_iter()
|
||||
.map(|value| value.into_string("findSound result"))
|
||||
.collect::<Result<Vec<String>>>()
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,10 @@ mod transport;
|
||||
mod value;
|
||||
|
||||
pub use bus::{Device, DeviceBus};
|
||||
pub use devices::redstone::{ParseSideError, Redstone, Side};
|
||||
pub use devices::{
|
||||
BlockOperations, Cpu, EnergyStorage, FileImportExport, FluidHandler, ImportedFileInfo,
|
||||
InventoryOperations, ItemHandler, ParseSideError, Redstone, RobotSide, Side, SoundCard,
|
||||
};
|
||||
pub use error::{Error, Result};
|
||||
pub use rpc::{DeviceEntry, JsonValue, MethodEntry, MethodParameter};
|
||||
pub use value::{IntoJsonArgs, IntoJsonValue};
|
||||
|
||||
87
src/value.rs
87
src/value.rs
@@ -1,5 +1,7 @@
|
||||
use crate::error::{Error, Result};
|
||||
use crate::rpc::JsonValue;
|
||||
use miniserde::json::Number;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// Convert common Rust types into `JsonValue`.
|
||||
pub trait IntoJsonValue {
|
||||
@@ -125,3 +127,88 @@ where
|
||||
self.iter().cloned().map(IntoJsonValue::into_json).collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience conversions from raw JSON values returned by the bus.
|
||||
pub trait JsonValueExt {
|
||||
fn into_bool(self, context: &str) -> Result<bool>;
|
||||
fn into_i64(self, context: &str) -> Result<i64>;
|
||||
fn into_u64(self, context: &str) -> Result<u64>;
|
||||
fn into_f64(self, context: &str) -> Result<f64>;
|
||||
fn into_string(self, context: &str) -> Result<String>;
|
||||
fn into_array(self, context: &str) -> Result<Vec<JsonValue>>;
|
||||
fn into_object(self, context: &str) -> Result<BTreeMap<String, JsonValue>>;
|
||||
}
|
||||
|
||||
impl JsonValueExt for JsonValue {
|
||||
fn into_bool(self, context: &str) -> Result<bool> {
|
||||
match self {
|
||||
JsonValue::Bool(value) => Ok(value),
|
||||
other => Err(Error::Protocol(format!(
|
||||
"expected boolean from {context}, got {other:?}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
fn into_i64(self, context: &str) -> Result<i64> {
|
||||
match self {
|
||||
JsonValue::Number(Number::I64(value)) => Ok(value),
|
||||
JsonValue::Number(Number::U64(value)) => value.try_into().map_err(|_| {
|
||||
Error::Protocol(format!("{context} returned value too large for i64"))
|
||||
}),
|
||||
JsonValue::Number(Number::F64(value)) => Ok(value as i64),
|
||||
other => Err(Error::Protocol(format!(
|
||||
"expected integer from {context}, got {other:?}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
fn into_u64(self, context: &str) -> Result<u64> {
|
||||
match self {
|
||||
JsonValue::Number(Number::U64(value)) => Ok(value),
|
||||
JsonValue::Number(Number::I64(value)) if value >= 0 => Ok(value as u64),
|
||||
JsonValue::Number(Number::F64(value)) if value >= 0.0 => Ok(value as u64),
|
||||
other => Err(Error::Protocol(format!(
|
||||
"expected unsigned integer from {context}, got {other:?}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
fn into_f64(self, context: &str) -> Result<f64> {
|
||||
match self {
|
||||
JsonValue::Number(Number::F64(value)) => Ok(value),
|
||||
JsonValue::Number(Number::I64(value)) => Ok(value as f64),
|
||||
JsonValue::Number(Number::U64(value)) => Ok(value as f64),
|
||||
other => Err(Error::Protocol(format!(
|
||||
"expected floating point number from {context}, got {other:?}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
fn into_string(self, context: &str) -> Result<String> {
|
||||
match self {
|
||||
JsonValue::String(value) => Ok(value),
|
||||
other => Err(Error::Protocol(format!(
|
||||
"expected string from {context}, got {other:?}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
fn into_array(self, context: &str) -> Result<Vec<JsonValue>> {
|
||||
match self {
|
||||
JsonValue::Array(array) => Ok(array.into_iter().collect()),
|
||||
JsonValue::Null => Ok(Vec::new()),
|
||||
other => Err(Error::Protocol(format!(
|
||||
"expected array from {context}, got {other:?}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
fn into_object(self, context: &str) -> Result<BTreeMap<String, JsonValue>> {
|
||||
match self {
|
||||
JsonValue::Object(object) => Ok(object.into_iter().collect()),
|
||||
other => Err(Error::Protocol(format!(
|
||||
"expected object from {context}, got {other:?}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user