This document describes the improved architecture of the ProjectorController project, focusing on extensibility and ease of adding support for new projector models.
The refactored architecture addresses several key issues:
- Configuration Flexibility: Serial port settings are now configurable instead of hardcoded
- Protocol Abstraction: Different projector command protocols are separated from implementation
- Capability Interfaces: Clear contracts for what features a projector supports
- Factory Pattern: Centralized creation of different projector types
- Extensibility: Easy to add new projector models with different protocols
ProjectorConfig - Builder pattern for configuring connection parameters:
ProjectorConfig config = new ProjectorConfig.Builder()
.portName("/dev/ttyUSB0")
.baudRate(115200)
.dataBits(DataBits.DATABITS_8)
.stopBits(StopBits.STOPBITS_1)
.parity(Parity.PARITY_NONE)
.charset(StandardCharsets.US_ASCII)
.responseTerminator(">")
.build();Pre-configured factory methods are available for common projector types:
ProjectorConfig config = ProjectorConfig.createBenQConfig("/dev/ttyUSB0");CommandProtocol interface defines how to format commands for different projector types.
Different manufacturers use different command formats:
- BenQ:
\r*pow=on#\rwith>terminator - Epson:
PWR ON\rwith:terminator - Sony: May use different formats entirely
Example implementations:
BenQProtocol- For BenQ projectorsEpsonProtocol- For Epson projectors (demonstration)
Projectors implement capability interfaces based on what features they support:
PowerControl- Turn on/off, check power stateSourceControl- Switch input sourcesCommandCapability- Send raw commands for advanced usage
This allows clients to check at runtime what a projector can do:
if (projector instanceof PowerControl) {
((PowerControl) projector).turnOn();
}Projector abstract class provides:
- Common message queuing functionality
- Protocol and configuration management
- Port management
All projector implementations extend this class.
ProjectorFactory provides centralized projector instantiation:
// Simple creation with default port
Projector projector = ProjectorFactory.createProjector(ProjectorType.BENQ_W1070);
// Creation with specific port
Projector projector = ProjectorFactory.createProjector(ProjectorType.BENQ_W1070, "/dev/ttyUSB0");
// Creation with custom configuration
ProjectorConfig config = ProjectorConfig.createBenQConfig("/dev/ttyUSB0");
Projector projector = ProjectorFactory.createProjector(ProjectorType.BENQ_W1070, config);Adding support for a new projector is now straightforward. Here's how:
public class SonyProtocol implements CommandProtocol {
@Override
public String getPowerOnCommand() {
return "POWER ON\r\n";
}
@Override
public String getPowerOffCommand() {
return "POWER OFF\r\n";
}
// ... implement other methods
@Override
public String getResponseTerminator() {
return "\r\n";
}
}public class SonyVPL extends Projector implements PowerControl, SourceControl {
public SonyVPL(String portName) throws ... {
super.projectorName = "Sony VPL";
ProjectorConfig config = new ProjectorConfig.Builder()
.portName(portName)
.baudRate(9600)
// ... other Sony-specific settings
.build();
super.initialize(config, new SonyProtocol());
}
@Override
public void turnOn() {
// Implementation using protocol.getPowerOnCommand()
}
// ... implement other capability methods
}- Add enum value to
ProjectorFactory.ProjectorType:
public enum ProjectorType {
BENQ_W1070,
EPSON_GENERIC,
SONY_VPL, // New projector type
}- Add cases to factory methods:
case SONY_VPL:
return new SonyVPL(portName);That's it! The new projector type is now fully integrated.
- Connection settings are separate from projector logic
- Command protocols are separate from projector implementations
- Capabilities are clearly defined interfaces
- Easy to add new projector models
- Easy to add new capabilities
- Easy to support different protocols
- Runtime configuration of connection parameters
- Runtime checking of projector capabilities
- Multiple ways to instantiate projectors
- Clear structure and organization
- Well-defined interfaces
- Single responsibility for each class
- Easy to mock protocols for testing
- Easy to test individual capabilities
- Configuration can be injected for testing
Projector projector = ProjectorFactory.createProjector(ProjectorType.BENQ_W1070);
if (projector instanceof PowerControl) {
PowerControl pc = (PowerControl) projector;
pc.turnOn();
}
projector.closePorts();ProjectorConfig config = new ProjectorConfig.Builder()
.portName("COM3")
.baudRate(115200)
.build();
Projector projector = ProjectorFactory.createProjector(
ProjectorType.BENQ_W1070,
config
);Projector benq = ProjectorFactory.createProjector(
ProjectorType.BENQ_W1070,
"/dev/ttyUSB0"
);
Projector epson = ProjectorFactory.createProjector(
ProjectorType.EPSON_GENERIC,
"/dev/ttyUSB1"
);
// Both projectors can be controlled through the same interfaces
if (benq instanceof PowerControl) {
((PowerControl) benq).turnOn();
}
if (epson instanceof PowerControl) {
((PowerControl) epson).turnOn();
}The existing W1070 class has been updated to use the new architecture while maintaining compatibility:
// Old way still works
W1070 projector = new W1070();
projector.turnOn();
projector.closePorts();
// New way with more flexibility
W1070 projector = new W1070("/dev/ttyUSB0");
// or
W1070 projector = new W1070(customConfig);Possible future improvements:
- More Capability Interfaces: Add interfaces for audio control, picture settings, etc.
- Configuration Files: Load projector configurations from JSON/XML files
- Auto-Detection: Automatically detect projector type on a given port
- Command Queue Priority: Support for priority-based command queuing
- Async Operations: Support for asynchronous command execution with CompletableFuture
- Event System: Event-driven architecture for projector state changes
The improved architecture makes it significantly easier to:
- Add support for new projector models
- Configure connection parameters
- Test different components
- Maintain and extend the codebase
The key is the separation of concerns: configuration, protocol, and implementation are now independent, making each easier to work with and modify.