---
import '../../styles/global.css';
---
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width" />
<meta name="generator" content={Astro.generator} />
<title>Multi-Device Management - PoKeys Examples</title>
</head>
<body class="bg-gray-900 text-white">
<nav class="fixed top-0 w-full z-50 bg-gray-900/80 backdrop-blur-md border-b border-gray-800">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between items-center py-4">
<a href="/core/" class="text-2xl font-bold bg-gradient-to-r from-blue-400 to-purple-500 bg-clip-text text-transparent">
PoKeys
</a>
<div class="hidden md:flex space-x-8">
<a href="/core/" class="hover:text-blue-400 transition-colors">Home</a>
<a href="/core/examples" class="hover:text-blue-400 transition-colors">Examples</a>
<a href="https://github.com/pokeys-toolkit/core" class="hover:text-blue-400 transition-colors">GitHub</a>
</div>
</div>
</div>
</nav>
<div class="pt-20 min-h-screen bg-gray-900">
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<div class="mb-12">
<h1 class="text-4xl font-bold mb-4 bg-gradient-to-r from-indigo-400 to-cyan-500 bg-clip-text text-transparent">
Multi-Device Management
</h1>
<p class="text-xl text-gray-400">
Learn how to manage multiple PoKeys devices simultaneously for complex automation systems
</p>
</div>
<div class="bg-gradient-to-r from-indigo-900/20 to-cyan-900/20 border border-indigo-500/30 rounded-lg p-6 mb-8">
<h2 class="text-2xl font-bold mb-4 text-indigo-400">What You'll Learn</h2>
<ul class="text-gray-300 space-y-2">
<li>• Discover and connect to multiple PoKeys devices</li>
<li>• Coordinate operations across multiple devices</li>
<li>• Handle device-specific configurations and capabilities</li>
<li>• Implement robust error handling for multi-device systems</li>
</ul>
</div>
<div class="mb-8">
<h2 class="text-2xl font-bold mb-4 text-white">Basic Multi-Device Discovery</h2>
<div class="bg-gray-800/50 border border-gray-700 rounded-lg p-6">
<pre class="text-sm text-gray-300 overflow-x-auto"><code>use pokeys_lib::*;
use std::collections::HashMap;
fn main() -> Result<()> {
// Discover all connected devices
let usb_count = enumerate_usb_devices()?;
let network_devices = discover_network_devices(5000)?; // 5 second timeout
println!("Found {} USB devices", usb_count);
println!("Found {} network devices", network_devices.len());
// Connect to all USB devices
let mut devices = HashMap::new();
for device_id in 0..usb_count {
match connect_to_device(device_id) {
Ok(device) => {
let info = device.get_device_info()?;
println!("Connected to USB device {}: {}", device_id, info.device_name);
devices.insert(format!("usb_{}", device_id), device);
},
Err(e) => println!("Failed to connect to USB device {}: {}", device_id, e),
}
}
// Connect to network devices
for (i, net_device) in network_devices.iter().enumerate() {
match connect_to_network_device(&net_device.ip_address, net_device.port) {
Ok(device) => {
let info = device.get_device_info()?;
println!("Connected to network device {}: {}", net_device.ip_address, info.device_name);
devices.insert(format!("net_{}", i), device);
},
Err(e) => println!("Failed to connect to {}: {}", net_device.ip_address, e),
}
}
println!("Successfully connected to {} devices total", devices.len());
// Perform operations on all devices
for (name, device) in &mut devices {
match configure_device_basics(device) {
Ok(_) => println!("Configured device: {}", name),
Err(e) => println!("Failed to configure {}: {}", name, e),
}
}
Ok(())
}
fn configure_device_basics(device: &mut PoKeysDevice) -> Result<()> {
// Set pin 1 as digital output on all devices
device.set_pin_function(1, PinFunction::DigitalOutput)?;
device.set_digital_output(1, false)?;
Ok(())
}</code></pre>
</div>
</div>
<div class="mb-8">
<h2 class="text-2xl font-bold mb-4 text-white">Advanced: Coordinated Device Control</h2>
<div class="bg-gray-800/50 border border-gray-700 rounded-lg p-6">
<pre class="text-sm text-gray-300 overflow-x-auto"><code>use pokeys_lib::*;
use std::collections::HashMap;
use std::thread;
use std::time:{Duration, Instant};
struct DeviceManager {
devices: HashMap<String, PoKeysDevice>,
device_roles: HashMap<String, DeviceRole>,
}
#[derive(Clone)]
enum DeviceRole {
InputController, // Reads sensors and inputs
OutputController, // Controls actuators and outputs
CommunicationHub, // Handles SPI/I2C communication
}
impl DeviceManager {
fn new() -> Result<Self> {
let mut manager = DeviceManager {
devices: HashMap::new(),
device_roles: HashMap::new(),
};
manager.discover_and_assign_devices()?;
Ok(manager)
}
fn discover_and_assign_devices(&mut self) -> Result<()> {
let usb_count = enumerate_usb_devices()?;
for device_id in 0..usb_count {
let device = connect_to_device(device_id)?;
let info = device.get_device_info()?;
let device_name = format!("device_{}", device_id);
// Assign roles based on device capabilities or serial number
let role = match device_id {
0 => DeviceRole::InputController,
1 => DeviceRole::OutputController,
_ => DeviceRole::CommunicationHub,
};
println!("Assigned {} as {:?}", device_name, role);
self.devices.insert(device_name.clone(), device);
self.device_roles.insert(device_name, role);
}
Ok(())
}
fn configure_all_devices(&mut self) -> Result<()> {
for (name, device) in &mut self.devices {
let role = self.device_roles.get(name).unwrap();
match role {
DeviceRole::InputController => self.configure_input_device(device)?,
DeviceRole::OutputController => self.configure_output_device(device)?,
DeviceRole::CommunicationHub => self.configure_communication_device(device)?,
}
println!("Configured {} for role {:?}", name, role);
}
Ok(())
}
fn configure_input_device(&self, device: &mut PoKeysDevice) -> Result<()> {
// Configure pins for input sensing
for pin in 1..=10 {
device.set_pin_function(pin, PinFunction::DigitalInput)?;
}
// Configure analog inputs
for pin in 41..=47 {
device.set_pin_function(pin, PinFunction::AnalogInput)?;
}
// Configure encoders
device.configure_encoder(0, 11, 12, EncoderOptions::with_4x_sampling())?;
Ok(())
}
fn configure_output_device(&self, device: &mut PoKeysDevice) -> Result<()> {
// Configure digital outputs
for pin in 1..=20 {
device.set_pin_function(pin, PinFunction::DigitalOutput)?;
device.set_digital_output(pin, false)?;
}
// Configure PWM outputs
for channel in 0..6 {
let pin = 21 + channel;
device.set_pin_function(pin, PinFunction::PwmOutput)?;
device.set_pwm_duty_cycle(channel as u8, 0.0)?;
}
Ok(())
}
fn configure_communication_device(&self, device: &mut PoKeysDevice) -> Result<()> {
// Configure SPI
let spi_config = SpiConfiguration {
clock_frequency: 1_000_000,
mode: SpiMode::Mode0,
bit_order: SpiBitOrder::MsbFirst,
};
device.configure_spi(spi_config)?;
// Configure I2C
device.configure_i2c(100000)?; // 100kHz
// Configure CS pins for SPI devices
for pin in 10..=15 {
device.set_pin_function(pin, PinFunction::DigitalOutput)?;
device.set_digital_output(pin, true)?; // CS idle high
}
Ok(())
}
fn run_coordinated_operation(&mut self) -> Result<()> {
println!("Starting coordinated multi-device operation...");
let start_time = Instant::now();
let mut cycle_count = 0;
loop {
// Read inputs from input controller
if let Some(input_device) = self.get_device_by_role(DeviceRole::InputController) {
let sensor_values = self.read_all_sensors(input_device)?;
// Process sensor data and determine outputs
let output_commands = self.process_sensor_data(sensor_values);
// Send commands to output controller
if let Some(output_device) = self.get_device_by_role(DeviceRole::OutputController) {
self.execute_output_commands(output_device, output_commands)?;
}
// Handle communication tasks
if let Some(comm_device) = self.get_device_by_role(DeviceRole::CommunicationHub) {
self.handle_communication_tasks(comm_device)?;
}
}
cycle_count += 1;
if cycle_count % 100 == 0 {
let elapsed = start_time.elapsed();
println!("Completed {} cycles in {:.2}s", cycle_count, elapsed.as_secs_f64());
}
thread::sleep(Duration::from_millis(10)); // 100Hz update rate
}
}
fn get_device_by_role(&mut self, role: DeviceRole) -> Option<&mut PoKeysDevice> {
for (name, device_role) in &self.device_roles {
if matches!(device_role, role) {
return self.devices.get_mut(name);
}
}
None
}
fn read_all_sensors(&mut self, device: &mut PoKeysDevice) -> Result<SensorData> {
let digital_inputs = device.get_digital_inputs(1, 10)?;
let analog_values = device.get_analog_inputs(41, 47)?;
let encoder_position = device.get_encoder_value(0)?;
Ok(SensorData {
digital_inputs,
analog_values,
encoder_position,
})
}
fn process_sensor_data(&self, data: SensorData) -> OutputCommands {
// Simple example: control outputs based on inputs
let mut commands = OutputCommands::default();
// Mirror digital inputs to first 10 outputs
commands.digital_outputs = data.digital_inputs.clone();
// Convert analog input to PWM duty cycle
if !data.analog_values.is_empty() {
let duty_cycle = data.analog_values[0] / 4095.0; // Normalize to 0-1
commands.pwm_values.push((0, duty_cycle));
}
commands
}
fn execute_output_commands(&mut self, device: &mut PoKeysDevice, commands: OutputCommands) -> Result<()> {
// Set digital outputs
for (pin, state) in commands.digital_outputs.iter().enumerate() {
device.set_digital_output((pin + 1) as u8, *state)?;
}
// Set PWM outputs
for (channel, duty_cycle) in commands.pwm_values {
device.set_pwm_duty_cycle(channel, duty_cycle)?;
}
Ok(())
}
fn handle_communication_tasks(&mut self, device: &mut PoKeysDevice) -> Result<()> {
// Example: Read temperature sensor via I2C
match device.i2c_read(0x48, 2) { // Common temp sensor address
Ok(data) => {
let temp = ((data[0] as u16) << 8 | data[1] as u16) as f32 / 256.0;
if temp > 50.0 { // Temperature threshold
println!("High temperature detected: {:.1}°C", temp);
}
},
Err(_) => {} // Sensor not connected, ignore
}
Ok(())
}
}
#[derive(Default)]
struct SensorData {
digital_inputs: Vec<bool>,
analog_values: Vec<f32>,
encoder_position: i32,
}
#[derive(Default)]
struct OutputCommands {
digital_outputs: Vec<bool>,
pwm_values: Vec<(u8, f32)>,
}
fn main() -> Result<()> {
let mut manager = DeviceManager::new()?;
manager.configure_all_devices()?;
manager.run_coordinated_operation()?;
Ok(())
}</code></pre>
</div>
</div>
<div class="mb-8">
<h2 class="text-2xl font-bold mb-4 text-white">Robust Error Handling</h2>
<div class="bg-gray-800/50 border border-gray-700 rounded-lg p-6">
<pre class="text-sm text-gray-300 overflow-x-auto"><code>use pokeys_lib::*;
use std::collections::HashMap;
use std::time:{Duration, Instant};
struct RobustDeviceManager {
devices: HashMap<String, Option<PoKeysDevice>>,
last_successful_contact: HashMap<String, Instant>,
reconnect_attempts: HashMap<String, u32>,
}
impl RobustDeviceManager {
fn new() -> Self {
RobustDeviceManager {
devices: HashMap::new(),
last_successful_contact: HashMap::new(),
reconnect_attempts: HashMap::new(),
}
}
fn discover_devices(&mut self) -> Result<()> {
// Try USB devices
match enumerate_usb_devices() {
Ok(count) => {
for device_id in 0..count {
let device_name = format!("usb_{}", device_id);
match connect_to_device(device_id) {
Ok(device) => {
self.devices.insert(device_name.clone(), Some(device));
self.last_successful_contact.insert(device_name.clone(), Instant::now());
self.reconnect_attempts.insert(device_name, 0);
println!("Connected to USB device {}", device_id);
},
Err(e) => {
println!("Failed to connect to USB device {}: {}", device_id, e);
self.devices.insert(device_name, None);
}
}
}
},
Err(e) => println!("Failed to enumerate USB devices: {}", e),
}
// Try network devices
match discover_network_devices(3000) {
Ok(network_devices) => {
for (i, net_device) in network_devices.iter().enumerate() {
let device_name = format!("net_{}", i);
match connect_to_network_device(&net_device.ip_address, net_device.port) {
Ok(device) => {
self.devices.insert(device_name.clone(), Some(device));
self.last_successful_contact.insert(device_name.clone(), Instant::now());
self.reconnect_attempts.insert(device_name, 0);
println!("Connected to network device {}", net_device.ip_address);
},
Err(e) => {
println!("Failed to connect to {}: {}", net_device.ip_address, e);
self.devices.insert(device_name, None);
}
}
}
},
Err(e) => println!("Failed to discover network devices: {}", e),
}
Ok(())
}
fn execute_with_retry<F, R>(&mut self, device_name: &str, operation: F) -> Option<R>
where
F: Fn(&mut PoKeysDevice) -> Result<R>,
{
if let Some(device_opt) = self.devices.get_mut(device_name) {
if let Some(device) = device_opt {
match operation(device) {
Ok(result) => {
// Success - update last contact time and reset retry count
self.last_successful_contact.insert(device_name.to_string(), Instant::now());
self.reconnect_attempts.insert(device_name.to_string(), 0);
return Some(result);
},
Err(e) => {
println!("Operation failed on {}: {}", device_name, e);
// Mark device as disconnected
*device_opt = None;
// Attempt reconnection
self.attempt_reconnection(device_name);
}
}
} else {
// Device is disconnected, try to reconnect
self.attempt_reconnection(device_name);
}
}
None
}
fn attempt_reconnection(&mut self, device_name: &str) {
let attempts = self.reconnect_attempts.get(device_name).unwrap_or(&0);
if *attempts < 5 { // Max 5 reconnection attempts
println!("Attempting to reconnect to {} (attempt {})", device_name, attempts + 1);
// Try to reconnect based on device type
let reconnected = if device_name.starts_with("usb_") {
if let Ok(device_id) = device_name[4..].parse::<u32>() {
connect_to_device(device_id).ok()
} else {
None
}
} else {
// For network devices, you'd need to store IP/port info
None
};
if let Some(device) = reconnected {
println!("Successfully reconnected to {}", device_name);
self.devices.insert(device_name.to_string(), Some(device));
self.last_successful_contact.insert(device_name.to_string(), Instant::now());
self.reconnect_attempts.insert(device_name.to_string(), 0);
} else {
self.reconnect_attempts.insert(device_name.to_string(), attempts + 1);
}
}
}
fn get_system_status(&self) -> SystemStatus {
let total_devices = self.devices.len();
let connected_devices = self.devices.values().filter(|d| d.is_some()).count();
let disconnected_devices = total_devices - connected_devices;
SystemStatus {
total_devices,
connected_devices,
disconnected_devices,
devices_with_errors: self.reconnect_attempts.values().filter(|&&attempts| attempts > 0).count(),
}
}
fn run_monitoring_loop(&mut self) -> Result<()> {
loop {
// Perform operations on all available devices
for device_name in self.devices.keys().cloned().collect::<Vec<_>>() {
// Example: Read pin 1 status
self.execute_with_retry(&device_name, |device| {
device.get_digital_input(1)
});
// Example: Toggle pin 2
self.execute_with_retry(&device_name, |device| {
let current = device.get_digital_output(2)?;
device.set_digital_output(2, !current)
});
}
// Print system status every 10 seconds
let status = self.get_system_status();
println!("System Status: {}/{} devices connected, {} errors",
status.connected_devices, status.total_devices, status.devices_with_errors);
std::thread::sleep(Duration::from_millis(100));
}
}
}
struct SystemStatus {
total_devices: usize,
connected_devices: usize,
disconnected_devices: usize,
devices_with_errors: usize,
}
fn main() -> Result<()> {
let mut manager = RobustDeviceManager::new();
manager.discover_devices()?;
manager.run_monitoring_loop()?;
Ok(())
}</code></pre>
</div>
</div>
<div class="mb-8">
<h2 class="text-2xl font-bold mb-4 text-white">Best Practices</h2>
<div class="space-y-4">
<div class="bg-green-900/20 border border-green-500/30 rounded-lg p-4">
<h3 class="font-semibold text-green-400 mb-2">Device Management</h3>
<ul class="text-gray-300 text-sm space-y-1">
<li>• Assign specific roles to each device for clear separation of concerns</li>
<li>• Implement robust error handling and automatic reconnection</li>
<li>• Monitor device health and connection status continuously</li>
<li>• Use device serial numbers or IP addresses for consistent identification</li>
</ul>
</div>
<div class="bg-blue-900/20 border border-blue-500/30 rounded-lg p-4">
<h3 class="font-semibold text-blue-400 mb-2">Performance Optimization</h3>
<ul class="text-gray-300 text-sm space-y-1">
<li>• Use bulk operations when possible to reduce communication overhead</li>
<li>• Implement appropriate update rates for different device functions</li>
<li>• Cache device capabilities and configurations to avoid repeated queries</li>
<li>• Consider using separate threads for each device to prevent blocking</li>
</ul>
</div>
<div class="bg-yellow-900/20 border border-yellow-500/30 rounded-lg p-4">
<h3 class="font-semibold text-yellow-400 mb-2">Safety Considerations</h3>
<ul class="text-gray-300 text-sm space-y-1">
<li>• Implement failsafe behavior when devices become unavailable</li>
<li>• Validate device capabilities before assigning critical functions</li>
<li>• Use watchdog timers to detect communication failures</li>
<li>• Implement emergency stop functionality across all devices</li>
</ul>
</div>
</div>
</div>
<div class="flex justify-between items-center pt-8 border-t border-gray-700">
<a href="/core/examples/spi-communication" class="flex items-center text-blue-400 hover:text-blue-300 transition-colors">
<span class="mr-2">←</span> SPI Communication
</a>
<a href="/core/examples" class="text-gray-400 hover:text-white transition-colors">
Back to Examples
</a>
<div class="w-32"></div>
</div>
</div>
</div>
</body>
</html>