wizctl 1.0.0 copy "wizctl: ^1.0.0" to clipboard
wizctl: ^1.0.0 copied to clipboard

A Dart package for controlling Philips WiZ smart lights over local network. Provides discovery, control, scenes, and group management along with CLI support.

example/wizctl_example.dart

// Example: Control WiZ lights with wizctl
//
// This example demonstrates all features of the wizctl library.

import 'package:wizctl/wizctl.dart';

void main() async {
  // ==========================================================================
  // Logging
  // ==========================================================================
  print('Enabling debug logging...\n');

  // Enable logging to see UDP packets and protocol details
  WizLogger.enable(WizLogLevel.info);

  // Custom log callback (optional)
  WizLogger.enable(
    WizLogLevel.debug,
    callback: (level, message) {
      print('[${level.name.toUpperCase()}] $message');
    },
  );

  // ==========================================================================
  // Discovery
  // ==========================================================================
  print('Discovering WiZ lights...\n');

  // Simple discovery with default settings
  var discovered = await WizDiscovery.discover(timeout: Duration(seconds: 5));

  // Discovery with custom retry configuration
  var discoveredWithRetry = await WizDiscovery.discover(
    timeout: Duration(seconds: 5),
    retry: RetryConfig.exponential(
      count: 3,
      initialInterval: Duration(milliseconds: 500),
      maxInterval: Duration(seconds: 2),
    ),
  );

  // Discovery on all network interfaces (useful with multiple adapters)
  // var allInterfacesLights = await WizDiscovery.discoverOnAllInterfaces();

  // Broadcast does not reach the lights on every network: some access points
  // filter it towards wireless clients, and Wi-Fi power save on the bulb means
  // broadcast frames only arrive on the access point's DTIM schedule. Unicast
  // is unaffected, so sweep the subnet one address at a time instead.
  if (discovered.isEmpty) {
    print('Broadcast found nothing, scanning the subnet...');
    discovered = await WizDiscovery.scanSubnet(
      timeout: Duration(seconds: 5),
      // Defaults to this machine's own /24; pass e.g. subnet: '192.168.1'
      // to search a different one.
    );
  }

  // Once you know where a light lives, ask it directly. This is quicker than a
  // sweep and far more reliable, because it never probes empty addresses.
  var known = [for (var light in discovered) light.ip];
  if (known.isNotEmpty) {
    var reachable = await WizDiscovery.probeAddresses(
      addresses: known,
      timeout: Duration(seconds: 3),
    );
    print('${reachable.length} of ${known.length} known light(s) responded');
  }

  if (discovered.isEmpty) {
    print('No lights found. Check WiFi connection.');
    return;
  }

  print('Found ${discovered.length} light(s):');
  for (var light in discovered) {
    print('  ${light.ip} (${light.mac})');
    if (light.moduleName != null) print('    Module: ${light.moduleName}');
    if (light.fwVersion != null) print('    Firmware: ${light.fwVersion}');
    if (light.bulbClass != null) {
      print('    Type: ${light.bulbClass!.displayName}');
      print('    Supports color: ${light.supportsColor}');
      print('    Supports temperature: ${light.supportsTemperature}');
    }
  }

  // ==========================================================================
  // Individual Light Control
  // ==========================================================================

  // Create a WizLight instance with the first discovered light
  var light = WizLight(discovered.first.ip);

  // Get current state
  var state = await light.getState();
  print('\nCurrent state: ${state.isOn ? 'ON' : 'OFF'}, ${state.dimming}%');
  if (state.scene != null) print('Scene: ${state.scene!.displayName}');

  // Check what mode the light is in
  print('Mode: ${_describeMode(state)}');

  // Get bulb configuration (cached after first call)
  var config = await light.getSystemConfig();
  print('\nBulb configuration:');
  print('  MAC: ${config.mac}');
  print('  Module: ${config.moduleName}');
  print('  Firmware: ${config.fwVersion}');
  if (config.bulbClass != null) {
    print('  Type: ${config.bulbClass!.displayName}');
    print('  Supports color: ${config.supportsColor}');
    print('  Supports temperature: ${config.supportsTemperature}');
    print('  Supports brightness: ${config.supportsBrightness}');
  }

  // Get supported Kelvin range
  var kelvinRange = await light.getKelvinRange();
  print('  Kelvin range: ${kelvinRange.min}K - ${kelvinRange.max}K');

  // ==========================================================================
  // Basic Control
  // ==========================================================================
  print('\nBasic control...');

  // Turn on with optional brightness
  await light.turnOn(brightness: 75);
  await Future.delayed(Duration(seconds: 1));

  // Toggle the light
  await light.toggle();
  await Future.delayed(Duration(seconds: 1));
  await light.toggle(); // Toggle back on
  await Future.delayed(Duration(seconds: 1));

  // Set brightness
  await light.setBrightness(50);
  await Future.delayed(Duration(seconds: 1));

  // ==========================================================================
  // Color Control
  // ==========================================================================
  print('\nColor control...');

  // RGB color
  await light.setColor(255, 100, 50);
  await Future.delayed(Duration(seconds: 2));

  // RGB with brightness
  await light.setColor(100, 200, 255, brightness: 80);
  await Future.delayed(Duration(seconds: 2));

  // Color temperature
  await light.setTemperature(3000);
  await Future.delayed(Duration(seconds: 2));

  // Color temperature with brightness
  await light.setTemperature(5000, brightness: 60);
  await Future.delayed(Duration(seconds: 2));

  // ==========================================================================
  // White LED Control
  // ==========================================================================
  print('\nWhite LED control...');

  // Set warm white intensity (0-255)
  await light.setWarmWhite(200, brightness: 70);
  await Future.delayed(Duration(seconds: 2));

  // Set cold white intensity (0-255)
  await light.setColdWhite(150, brightness: 70);
  await Future.delayed(Duration(seconds: 2));

  // Set both cold and warm white
  await light.setWhite(coldWhite: 100, warmWhite: 150, brightness: 60);
  await Future.delayed(Duration(seconds: 2));

  // ==========================================================================
  // Scenes
  // ==========================================================================
  print('\nScene control...');

  // List some available scenes
  print(
    'Available scenes: ${WizScene.values.take(5).map((s) => s.displayName).join(', ')}...',
  );

  // Apply the Cozy scene
  print('Applying "Cozy" scene...');
  await light.setScene(WizScene.cozy);
  await Future.delayed(Duration(seconds: 2));

  // Apply Sunset scene with custom brightness
  print('Applying "Sunset" scene with brightness 80...');
  await light.setScene(WizScene.sunset, brightness: 80);
  await Future.delayed(Duration(seconds: 2));

  // Apply a dynamic scene with custom speed (10-200)
  print('Applying "Party" scene with speed 150...');
  await light.setScene(WizScene.party, speed: 150);
  await Future.delayed(Duration(seconds: 2));

  // Set effect speed independently
  await light.setSpeed(100);
  await Future.delayed(Duration(seconds: 2));

  // ==========================================================================
  // ControlSignal - Combined Settings
  // ==========================================================================
  print('\nUsing ControlSignal for combined settings...');

  // Full control signal with multiple parameters
  await light.send(
    ControlSignal(state: true, dimming: 80, r: 200, g: 150, b: 255),
  );
  await Future.delayed(Duration(seconds: 2));

  // Factory constructors for common operations
  await light.send(ControlSignal.on());
  await light.send(ControlSignal.brightness(50));
  await light.send(ControlSignal.rgb(255, 100, 0, brightness: 70));
  await light.send(ControlSignal.temperature(4000, brightness: 60));
  await light.send(ControlSignal.scene(WizScene.ocean, brightness: 80));
  await light.send(ControlSignal.warmWhite(200));
  await light.send(ControlSignal.coldWhite(150));
  await light.send(ControlSignal.speed(100));
  await Future.delayed(Duration(seconds: 2));

  // ==========================================================================
  // Group Control (if multiple lights discovered)
  // ==========================================================================

  if (discoveredWithRetry.length > 1) {
    var lights = discoveredWithRetry.map((d) => WizLight(d.ip)).toList();

    print('\nControlling ${lights.length} lights together...');

    // Basic group operations
    await WizGroup.turnOn(lights, brightness: 75);
    await WizGroup.setColor(lights, 255, 100, 50);
    await WizGroup.setTemperature(lights, 4000);
    await WizGroup.setScene(lights, WizScene.cozy, brightness: 80);
    await WizGroup.setBrightness(lights, 60);
    await WizGroup.setWarmWhite(lights, 150);
    await WizGroup.setColdWhite(lights, 100);
    await WizGroup.setSpeed(lights, 100);
    await WizGroup.toggle(lights);

    // Send custom ControlSignal to all lights
    await WizGroup.send(lights, ControlSignal(state: true, dimming: 50));

    // Get states from all lights
    var states = await WizGroup.getStates(lights);
    for (var entry in states.entries) {
      print('  ${entry.key.ip}: ${entry.value?.isOn ?? false ? 'ON' : 'OFF'}');
    }

    // Check operation results for failures
    var results = await WizGroup.turnOn(lights);
    for (var result in results) {
      if (!result.success) {
        print('Failed: ${result.light.ip} - ${result.error}');
      }
    }

    await WizGroup.turnOff(lights);
  }

  // ==========================================================================
  // Error Handling
  // ==========================================================================
  print('\nError handling examples...');

  // Invalid brightness
  try {
    await light.setBrightness(150); // Invalid: must be 10-100
  } on WizArgumentError catch (e) {
    print('Caught WizArgumentError: ${e.message}');
  }

  // Invalid color value
  try {
    await light.setColor(300, 100, 50); // Invalid: must be 0-255
  } on WizArgumentError catch (e) {
    print('Caught WizArgumentError: ${e.message}');
  }

  // Invalid temperature
  try {
    await light.setTemperature(10000); // Invalid: must be 2200-6500
  } on WizArgumentError catch (e) {
    print('Caught WizArgumentError: ${e.message}');
  }

  // Timeout error (unreachable light)
  try {
    var fake = WizLight('192.168.1.254', timeout: Duration(seconds: 2));
    await fake.getState();
  } on WizTimeoutError catch (e) {
    print('Caught WizTimeoutError: ${e.message}');
  }

  // Connection error
  try {
    var fake = WizLight('999.999.999.999');
    await fake.getState();
  } on WizConnectionError catch (e) {
    print('Caught WizConnectionError: ${e.message}');
  }

  // ==========================================================================
  // Retry Configuration Examples
  // ==========================================================================
  print('\nRetry configuration examples...');

  // Retry configurations can be used with WizDiscovery.discover()
  // and are built into WizProtocol.send() for individual light communication.

  // No retries (single attempt)
  print('  RetryConfig.none() - single attempt');
  var noRetry = RetryConfig.none();
  print('    Count: ${noRetry.count}, Enabled: ${noRetry.enabled}');

  // Fixed interval retries
  print('  RetryConfig.fixed() - fixed intervals');
  var fixedRetry = RetryConfig.fixed(count: 3, interval: Duration(seconds: 1));
  print('    Count: ${fixedRetry.count}, Interval: ${fixedRetry.interval}');

  // Exponential backoff (default for the library)
  print('  RetryConfig.exponential() - exponential backoff');
  var expRetry = RetryConfig.exponential(
    count: 5,
    initialInterval: Duration(milliseconds: 750),
    maxInterval: Duration(seconds: 3),
  );
  print('    Count: ${expRetry.count}, Initial: ${expRetry.interval}');

  // The library uses default exponential backoff:
  // - 5 retries with exponential backoff
  // - Starting at 750ms, capping at 3s

  // ==========================================================================
  // System Commands
  // ==========================================================================
  print('\nSystem commands (commented out for safety)...');

  // Reboot the light (it will restart and reconnect)
  // await light.reboot();

  // Factory reset (WARNING: removes WiFi settings!)
  // await light.reset();

  // Clear cached configuration
  light.clearCache();

  // ==========================================================================
  // Cleanup
  // ==========================================================================
  print('\nTurning light off...');
  await light.turnOff();

  // Disable logging
  WizLogger.disable();

  print('\nDone!');
}

/// Describes the current mode of a light based on its state.
String _describeMode(LightState state) {
  if (state.isSceneMode) return 'Scene (${state.scene?.displayName})';
  if (state.isRgbMode) return 'RGB (${state.r}, ${state.g}, ${state.b})';
  if (state.isTemperatureMode) return 'Temperature (${state.temperature}K)';
  if (state.isWhiteMode) {
    return 'White (CW: ${state.coldWhite}, WW: ${state.warmWhite})';
  }
  return 'Unknown';
}
2
likes
160
points
82
downloads

Documentation

Documentation
API reference

Publisher

verified publisherbharatsharma.me

Weekly Downloads

A Dart package for controlling Philips WiZ smart lights over local network. Provides discovery, control, scenes, and group management along with CLI support.

Repository (GitHub)
View/report issues

Topics

#iot #smart-home #philips-wiz #lighting #udp

License

MIT (license)

Dependencies

args

More

Packages that depend on wizctl