lifxlan

package module
v0.1.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 7, 2020 License: BSD-3-Clause Imports: 13 Imported by: 0

README

PkgGoDev Go Report Card

LIFX LAN

This is a library that provides API implemented in Go for LIFX LAN Protocol.

Overview

The root package focuses on the base stuff, device discovery, and capabilities shared by all types of devices. Subpackages provide more concreted capabilities by different types of LIFX devices like light control and tile control.

Currently this library is not complete and implement all possible LIFX LAN Protocols is not the current goal of this library. The design choice for this library is that it exposes as much as possible, so another third party package can implement missing device APIs by wrapping Devices returned by this package. Please refer to the subpackage code for an example of extending device capabilities. The reason its split into subpackages is to make sure that it's extendible.

The main focus right now is on tile API support. The reason is that at the time of writing, although there are several Go projects implemented LIFX LAN Protocol available, none of them support tile APIs. Please refer to tile subpackage on GoDoc for more details.

All API with (potential) I/O calls takes a context arg and checks for (and in most cases, relies on) context cancellations.

The API is unstable right now, but I try very hard not to break them.

Examples

Besides examples on GoDoc, there are also some example command line apps in lifxlan-examples repository.

License

BSD License.

Documentation

Overview

Package lifxlan provides API implemented in Go for LIFX LAN Protocol:

https://lan.developer.lifx.com/v2.0/docs/

This package focuses on the base stuff, device discovery, and capabilities shared by all types of devices. For more concreted capabilities like light control and tile control, please refer to the subpackages.

Currently this package and its subpackages are not complete and implement all possible LIFX LAN Protocols is not the current goal of this package. The design choice for this package is that it exposes as much as possible, so another package can implement missing device APIs by wrapping a device returned by discovery using only exported functions. Please refer to the subpackages for an example of extending device capabilities.

The API is unstable right now, but the maintainer tries very hard not to break them.

Example (DeviceDiscovery)

This example demonstrates how to do device discovery.

package main

import (
	"context"
	"log"
	"sync"
	"time"

	"github.com/fishy/lifxlan"
)

func main() {
	// Config values that should be initialized with proper args in real code.
	var (
		// The target device you want to find.
		target lifxlan.Target
		// The discover timeout.
		timeout time.Duration
	)

	// It's important to be able to cancel the context.
	var ctx context.Context
	var cancel context.CancelFunc
	if timeout > 0 {
		ctx, cancel = context.WithTimeout(context.Background(), timeout)
	} else {
		ctx, cancel = context.WithCancel(context.Background())
	}
	defer cancel()

	deviceChan := make(chan lifxlan.Device)
	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		defer wg.Done()
		if err := lifxlan.Discover(ctx, deviceChan, ""); err != nil {
			if err != nil && err != context.Canceled && err != context.DeadlineExceeded {
				log.Fatalf("Discover failed: %v", err)
			}
		}
	}()

	for device := range deviceChan {
		if !device.Target().Matches(target) {
			continue
		}

		wg.Add(1)
		go func(device lifxlan.Device) {
			defer wg.Done()
			// TODO: handle device

			// If you are satisfied with the device(s) found,
			// you can cancel the context now:
			cancel()
		}(device)
	}

	wg.Wait()
}
Output:

Example (SendMessageWithAck)

This example demonstrates how to send a message and wait for the ack.

Please note that this example assumes that no other replies besides ack are expected. If this message will cause a response, you should write your own read loop instead of using WaitForAcks.

package main

import (
	"context"
	"log"

	"github.com/fishy/lifxlan"
)

func main() {
	// Should actually be a proper struct according to the Protocol definition.
	type payloadType struct{}
	// Config values that should be initialized with proper args in real code.
	var (
		// Should come with a timeout, or WaitForAcks might wait forever.
		ctx context.Context
		// The discovered device to use.
		device lifxlan.Device
		// The actual message type to be sent.
		message lifxlan.MessageType
		// The actual payload values.
		payload payloadType
	)

	conn, err := device.Dial()
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()

	select {
	default:
	case <-ctx.Done():
		log.Fatal(ctx.Err())
	}

	seq, err := device.Send(
		ctx,
		conn,
		lifxlan.FlagAckRequired,
		message,
		&payload, // could be nil if this message doesn't need payload.
	)
	if err != nil {
		log.Fatal(err)
	}

	if err := lifxlan.WaitForAcks(ctx, conn, device.Source(), seq); err != nil {
		log.Fatal(err)
	}
}
Output:

Example (SendMessageWithResponse)

This example demonstrates how to send a message and read the response.

package main

import (
	"bytes"
	"context"
	"encoding/binary"
	"log"

	"github.com/fishy/lifxlan"
)

func main() {
	// Should actually be proper structs according to the Protocol definition.
	type (
		payloadType     struct{}
		respPayloadType struct{}
	)
	// Config values that should be initialized with proper args in real code.
	var (
		// Should come with a timeout, or we might wait forever.
		ctx context.Context
		// The discovered device to use.
		device lifxlan.Device
		// The actual message type to be sent.
		message lifxlan.MessageType
		// The actual payload values.
		payload payloadType
		// The response message type.
		respMessage lifxlan.MessageType
	)

	conn, err := device.Dial()
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()

	select {
	default:
	case <-ctx.Done():
		log.Fatal(ctx.Err())
	}

	seq, err := device.Send(
		ctx,
		conn,
		0, // flags, not requiring ack because this message will get a response.
		message,
		&payload, // could be nil if this message doesn't need payload.
	)
	if err != nil {
		log.Fatal(err)
	}

	for {
		resp, err := lifxlan.ReadNextResponse(ctx, conn)
		if err != nil {
			log.Fatal(err)
		}
		if resp.Sequence != seq || resp.Source != device.Source() {
			continue
		}
		if resp.Message != respMessage {
			continue
		}

		var raw respPayloadType
		r := bytes.NewReader(resp.Payload)
		if err := binary.Read(r, binary.LittleEndian, &raw); err != nil {
			log.Fatal(err)
		}
		// TODO: handle payload value in raw
		return
	}
}
Output:

Index

Examples

Constants

View Source
const (
	KelvinWarm uint16 = 2500
	KelvinCool uint16 = 9000

	KelvinMin uint16 = KelvinWarm
	KelvinMax uint16 = KelvinCool
)

Color value boundaries and constants.

View Source
const (
	DefaultBroadcastHost = "255.255.255.255"
	DefaultBroadcastPort = "56700"
)

Default broadcast host and port.

View Source
const EmptyHardwareVersion = "(0, 0, 0)"

EmptyHardwareVersion is the constant to be compared against Device.HardwareVersion().String().

View Source
const EmptyLabel = ""

EmptyLabel is the constant to be compared against Device.Label().String().

View Source
const HeaderLength = 36

HeaderLength is the length of the header

View Source
const LabelLength = 32

LabelLength is the length of the raw label used in messages.

View Source
const ResponseReadBufferSize = 4096

ResponseReadBufferSize is the recommended buffer size to read UDP responses. It's big enough for all the payloads.

Variables

View Source
var ColorBlack = *FromColor(color.Black, 0)

ColorBlack is the black color.

View Source
var ProductMap = map[uint64]ParsedHardwareVersion{
	ProductMapKey(1, 1): {
		VendorName:  "LIFX",
		ProductName: "LIFX Original 1000",
		Color:       true,
		Infrared:    false,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2500,
		MaxKelvin:   9000,
	},
	ProductMapKey(1, 3): {
		VendorName:  "LIFX",
		ProductName: "LIFX Color 650",
		Color:       true,
		Infrared:    false,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2500,
		MaxKelvin:   9000,
	},
	ProductMapKey(1, 10): {
		VendorName:  "LIFX",
		ProductName: "LIFX White 800 (Low Voltage)",
		Color:       false,
		Infrared:    false,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2700,
		MaxKelvin:   6500,
	},
	ProductMapKey(1, 11): {
		VendorName:  "LIFX",
		ProductName: "LIFX White 800 (High Voltage)",
		Color:       false,
		Infrared:    false,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2700,
		MaxKelvin:   6500,
	},
	ProductMapKey(1, 15): {
		VendorName:  "LIFX",
		ProductName: "LIFX Color 1000",
		Color:       true,
		Infrared:    false,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2500,
		MaxKelvin:   9000,
	},
	ProductMapKey(1, 18): {
		VendorName:  "LIFX",
		ProductName: "LIFX White 900 BR30 (Low Voltage)",
		Color:       false,
		Infrared:    false,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2500,
		MaxKelvin:   9000,
	},
	ProductMapKey(1, 19): {
		VendorName:  "LIFX",
		ProductName: "LIFX White 900 BR30 (High Voltage)",
		Color:       false,
		Infrared:    false,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2500,
		MaxKelvin:   9000,
	},
	ProductMapKey(1, 20): {
		VendorName:  "LIFX",
		ProductName: "LIFX Color 1000 BR30",
		Color:       true,
		Infrared:    false,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2500,
		MaxKelvin:   9000,
	},
	ProductMapKey(1, 22): {
		VendorName:  "LIFX",
		ProductName: "LIFX Color 1000",
		Color:       true,
		Infrared:    false,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2500,
		MaxKelvin:   9000,
	},
	ProductMapKey(1, 27): {
		VendorName:  "LIFX",
		ProductName: "LIFX A19",
		Color:       true,
		Infrared:    false,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2500,
		MaxKelvin:   9000,
	},
	ProductMapKey(1, 28): {
		VendorName:  "LIFX",
		ProductName: "LIFX BR30",
		Color:       true,
		Infrared:    false,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2500,
		MaxKelvin:   9000,
	},
	ProductMapKey(1, 29): {
		VendorName:  "LIFX",
		ProductName: "LIFX A19 Night Vision",
		Color:       true,
		Infrared:    true,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2500,
		MaxKelvin:   9000,
	},
	ProductMapKey(1, 30): {
		VendorName:  "LIFX",
		ProductName: "LIFX BR30 Night Vision",
		Color:       true,
		Infrared:    true,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2500,
		MaxKelvin:   9000,
	},
	ProductMapKey(1, 31): {
		VendorName:  "LIFX",
		ProductName: "LIFX Z",
		Color:       true,
		Infrared:    false,
		MultiZone:   true,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2500,
		MaxKelvin:   9000,
	},
	ProductMapKey(1, 32): {
		VendorName:  "LIFX",
		ProductName: "LIFX Z",
		Color:       true,
		Infrared:    false,
		MultiZone:   true,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2500,
		MaxKelvin:   9000,
	},
	ProductMapKey(1, 36): {
		VendorName:  "LIFX",
		ProductName: "LIFX Downlight",
		Color:       true,
		Infrared:    false,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2500,
		MaxKelvin:   9000,
	},
	ProductMapKey(1, 37): {
		VendorName:  "LIFX",
		ProductName: "LIFX Downlight",
		Color:       true,
		Infrared:    false,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2500,
		MaxKelvin:   9000,
	},
	ProductMapKey(1, 38): {
		VendorName:  "LIFX",
		ProductName: "LIFX Beam",
		Color:       true,
		Infrared:    false,
		MultiZone:   true,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2500,
		MaxKelvin:   9000,
	},
	ProductMapKey(1, 39): {
		VendorName:  "LIFX",
		ProductName: "LIFX Downlight White To Warm",
		Color:       false,
		Infrared:    false,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   1500,
		MaxKelvin:   9000,
	},
	ProductMapKey(1, 40): {
		VendorName:  "LIFX",
		ProductName: "LIFX Downlight",
		Color:       true,
		Infrared:    false,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2500,
		MaxKelvin:   9000,
	},
	ProductMapKey(1, 43): {
		VendorName:  "LIFX",
		ProductName: "LIFX A19",
		Color:       true,
		Infrared:    false,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2500,
		MaxKelvin:   9000,
	},
	ProductMapKey(1, 44): {
		VendorName:  "LIFX",
		ProductName: "LIFX BR30",
		Color:       true,
		Infrared:    false,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2500,
		MaxKelvin:   9000,
	},
	ProductMapKey(1, 45): {
		VendorName:  "LIFX",
		ProductName: "LIFX A19 Night Vision",
		Color:       true,
		Infrared:    true,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2500,
		MaxKelvin:   9000,
	},
	ProductMapKey(1, 46): {
		VendorName:  "LIFX",
		ProductName: "LIFX BR30 Night Vision",
		Color:       true,
		Infrared:    true,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2500,
		MaxKelvin:   9000,
	},
	ProductMapKey(1, 49): {
		VendorName:  "LIFX",
		ProductName: "LIFX Mini Color",
		Color:       true,
		Infrared:    false,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2500,
		MaxKelvin:   9000,
	},
	ProductMapKey(1, 50): {
		VendorName:  "LIFX",
		ProductName: "LIFX Mini White To Warm",
		Color:       false,
		Infrared:    false,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   1500,
		MaxKelvin:   4000,
	},
	ProductMapKey(1, 51): {
		VendorName:  "LIFX",
		ProductName: "LIFX Mini White",
		Color:       false,
		Infrared:    false,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2700,
		MaxKelvin:   2700,
	},
	ProductMapKey(1, 52): {
		VendorName:  "LIFX",
		ProductName: "LIFX GU10",
		Color:       true,
		Infrared:    false,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2500,
		MaxKelvin:   9000,
	},
	ProductMapKey(1, 53): {
		VendorName:  "LIFX",
		ProductName: "LIFX GU10",
		Color:       true,
		Infrared:    false,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2500,
		MaxKelvin:   9000,
	},
	ProductMapKey(1, 55): {
		VendorName:  "LIFX",
		ProductName: "LIFX Tile",
		Color:       true,
		Infrared:    false,
		MultiZone:   false,
		Chain:       true,
		Matrix:      true,
		MinKelvin:   2500,
		MaxKelvin:   9000,
	},
	ProductMapKey(1, 57): {
		VendorName:  "LIFX",
		ProductName: "LIFX Candle",
		Color:       true,
		Infrared:    false,
		MultiZone:   false,
		Chain:       false,
		Matrix:      true,
		MinKelvin:   1500,
		MaxKelvin:   9000,
	},
	ProductMapKey(1, 59): {
		VendorName:  "LIFX",
		ProductName: "LIFX Mini Color",
		Color:       true,
		Infrared:    false,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2500,
		MaxKelvin:   9000,
	},
	ProductMapKey(1, 60): {
		VendorName:  "LIFX",
		ProductName: "LIFX Mini White To Warm",
		Color:       false,
		Infrared:    false,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   1500,
		MaxKelvin:   4000,
	},
	ProductMapKey(1, 61): {
		VendorName:  "LIFX",
		ProductName: "LIFX Mini White",
		Color:       false,
		Infrared:    false,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2700,
		MaxKelvin:   2700,
	},
	ProductMapKey(1, 62): {
		VendorName:  "LIFX",
		ProductName: "LIFX A19",
		Color:       true,
		Infrared:    false,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2500,
		MaxKelvin:   9000,
	},
	ProductMapKey(1, 63): {
		VendorName:  "LIFX",
		ProductName: "LIFX BR30",
		Color:       true,
		Infrared:    false,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2500,
		MaxKelvin:   9000,
	},
	ProductMapKey(1, 64): {
		VendorName:  "LIFX",
		ProductName: "LIFX A19 Night Vision",
		Color:       true,
		Infrared:    true,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2500,
		MaxKelvin:   9000,
	},
	ProductMapKey(1, 65): {
		VendorName:  "LIFX",
		ProductName: "LIFX BR30 Night Vision",
		Color:       true,
		Infrared:    true,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2500,
		MaxKelvin:   9000,
	},
	ProductMapKey(1, 66): {
		VendorName:  "LIFX",
		ProductName: "LIFX Mini White",
		Color:       false,
		Infrared:    false,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2700,
		MaxKelvin:   2700,
	},
	ProductMapKey(1, 68): {
		VendorName:  "LIFX",
		ProductName: "LIFX Candle",
		Color:       false,
		Infrared:    false,
		MultiZone:   false,
		Chain:       false,
		Matrix:      true,
		MinKelvin:   1500,
		MaxKelvin:   9000,
	},
	ProductMapKey(1, 70): {
		VendorName:  "LIFX",
		ProductName: "LIFX Switch",
		Color:       false,
		Infrared:    false,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
	},
	ProductMapKey(1, 81): {
		VendorName:  "LIFX",
		ProductName: "LIFX Candle White To Warm",
		Color:       false,
		Infrared:    false,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2200,
		MaxKelvin:   6500,
	},
	ProductMapKey(1, 82): {
		VendorName:  "LIFX",
		ProductName: "LIFX Filament Clear",
		Color:       false,
		Infrared:    false,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2100,
		MaxKelvin:   2100,
	},
	ProductMapKey(1, 85): {
		VendorName:  "LIFX",
		ProductName: "LIFX Filament Amber",
		Color:       false,
		Infrared:    false,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2000,
		MaxKelvin:   2000,
	},
	ProductMapKey(1, 87): {
		VendorName:  "LIFX",
		ProductName: "LIFX Mini White",
		Color:       false,
		Infrared:    false,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2700,
		MaxKelvin:   2700,
	},
	ProductMapKey(1, 88): {
		VendorName:  "LIFX",
		ProductName: "LIFX Mini White",
		Color:       false,
		Infrared:    false,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2700,
		MaxKelvin:   2700,
	},
	ProductMapKey(1, 89): {
		VendorName:  "LIFX",
		ProductName: "LIFX Switch",
		Color:       false,
		Infrared:    false,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
	},
	ProductMapKey(1, 90): {
		VendorName:  "LIFX",
		ProductName: "LIFX Clean",
		Color:       true,
		Infrared:    false,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2500,
		MaxKelvin:   9000,
	},
	ProductMapKey(1, 91): {
		VendorName:  "LIFX",
		ProductName: "LIFX Color",
		Color:       true,
		Infrared:    false,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2500,
		MaxKelvin:   9000,
	},
	ProductMapKey(1, 92): {
		VendorName:  "LIFX",
		ProductName: "LIFX Color",
		Color:       true,
		Infrared:    false,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2500,
		MaxKelvin:   9000,
	},
	ProductMapKey(1, 94): {
		VendorName:  "LIFX",
		ProductName: "LIFX BR30",
		Color:       true,
		Infrared:    false,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2500,
		MaxKelvin:   9000,
	},
	ProductMapKey(1, 96): {
		VendorName:  "LIFX",
		ProductName: "LIFX Candle White To Warm",
		Color:       false,
		Infrared:    false,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2200,
		MaxKelvin:   6500,
	},
	ProductMapKey(1, 97): {
		VendorName:  "LIFX",
		ProductName: "LIFX A19",
		Color:       true,
		Infrared:    false,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2500,
		MaxKelvin:   9000,
	},
	ProductMapKey(1, 98): {
		VendorName:  "LIFX",
		ProductName: "LIFX BR30",
		Color:       true,
		Infrared:    false,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2500,
		MaxKelvin:   9000,
	},
	ProductMapKey(1, 99): {
		VendorName:  "LIFX",
		ProductName: "LIFX Clean",
		Color:       true,
		Infrared:    false,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2500,
		MaxKelvin:   9000,
	},
	ProductMapKey(1, 100): {
		VendorName:  "LIFX",
		ProductName: "LIFX Filament Clear",
		Color:       false,
		Infrared:    false,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2100,
		MaxKelvin:   2100,
	},
	ProductMapKey(1, 101): {
		VendorName:  "LIFX",
		ProductName: "LIFX Filament Amber",
		Color:       false,
		Infrared:    false,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2000,
		MaxKelvin:   2000,
	},
	ProductMapKey(1, 109): {
		VendorName:  "LIFX",
		ProductName: "LIFX A19 Night Vision",
		Color:       true,
		Infrared:    true,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2500,
		MaxKelvin:   9000,
	},
	ProductMapKey(1, 110): {
		VendorName:  "LIFX",
		ProductName: "LIFX BR30 Night Vision",
		Color:       true,
		Infrared:    true,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2500,
		MaxKelvin:   9000,
	},
	ProductMapKey(1, 111): {
		VendorName:  "LIFX",
		ProductName: "LIFX A19 Night Vision",
		Color:       true,
		Infrared:    true,
		MultiZone:   false,
		Chain:       false,
		Matrix:      false,
		MinKelvin:   2500,
		MaxKelvin:   9000,
	},
}

ProductMap is the map of all known hardwares.

If a new product is added and this file is not updated yet, you can add it to the map by yourself, for example:

func init() {
    key := lifxlan.ProductMapKey(newVID, newPID)
    lifxlan.ProductMap[key] = ParsedHardwareVersion{
        // Fill in values
    }
}

The content of this map was fetched from https://github.com/LIFX/products/blob/master/products.json and generated by https://github.com/fishy/lifxlan/tree/master/cmd/gen-product-map

View Source
var UDPReadTimeout = time.Millisecond * 100

UDPReadTimeout is the read timeout we use to read all the UDP messages.

In some functions (e.g. Discover), the function will simply use the timeout to check context cancellation and continue reading, instead of return upon timeout.

It's intentionally defined as variable instead of constant, so the user could adjust it if needed.

Functions

func CheckTimeoutError

func CheckTimeoutError(err error) bool

CheckTimeoutError returns true if err is caused by timeout in net package.

func Discover

func Discover(
	ctx context.Context,
	devices chan Device,
	broadcastHost string,
) error

Discover discovers lifx products in the lan.

When broadcastHost is empty (""), DefaultBroadcastHost will be used instead. In most cases that should just work. But if your network has special settings, you can override it via the arg.

The function will write discovered devices into devices channel. It's the caller's responsibility to read from channel timely to avoid blocking writing. The function is guaranteed to close the channel upon retuning, so the caller could just range over the channel for reading, e.g.

devices := make(chan Device)
go func() {
  if err := Discover(ctx, devices, ""); err != nil {
    if err != context.DeadlineExceeded {
      // handle error
    }
  }
}()
for device := range devices {
  // Do something with device
}

The function will only return upon error or when ctx is cancelled. It's the caller's responsibility to make sure that the context is cancelled (e.g. Use context.WithTimeout).

func GenerateMessage

func GenerateMessage(
	tagged TaggedHeader,
	source uint32,
	target Target,
	flags AckResFlag,
	sequence uint8,
	message MessageType,
	payload []byte,
) ([]byte, error)

GenerateMessage generates the message to send.

func GetReadDeadline

func GetReadDeadline() time.Time

GetReadDeadline returns a value can be used in net.Conn.SetReadDeadline from UDPReadTimeout value.

func ProductMapKey

func ProductMapKey(vendor, product uint32) uint64

ProductMapKey generates key for ProductMap based on vendor and product ids.

func RandomSource

func RandomSource() uint32

RandomSource generates a random number to be used as source. It's guaranteed to be non-zero.

func WaitForAcks

func WaitForAcks(
	ctx context.Context,
	conn net.Conn,
	source uint32,
	sequences ...uint8,
) error

WaitForAcks helps device API implementations to wait for acks.

It blocks until acks for all sequences are received, in which case it returns nil error. It also returns when the context is cancelled.

This function drops all received messages that is not an ack, or ack messages that the sequence and source don't match. Therefore, there shouldn't be more than one WaitForAcks functions running for the same connection at the same time, and this function should only be used when no other responses are expected.

If this function returns an error, the error would be of type *WaitForAcksError.

Types

type AckResFlag

type AckResFlag uint8

AckResFlag is the 8-bit header that could include:

- ack_required: if set all sent messages will expect an ack response.

- res_required: if set all sent messages will expect a response.

const (
	FlagResRequired AckResFlag = 1 << iota
	FlagAckRequired
)

AckResFlag values.

type Color

type Color struct {
	Hue        uint16
	Saturation uint16
	Brightness uint16
	Kelvin     uint16
}

Color is the HSBK color type used in lifx lan API.

https://lan.developer.lifx.com/v2.0/docs/light-messages#section-hsbk

func FromColor

func FromColor(c color.Color, kelvin uint16) *Color

FromColor converts a standard library color into HSBK color.

Alpha channel will be ignored and kelvin value will be added.

func (*Color) Sanitize

func (c *Color) Sanitize()

Sanitize tries to sanitize the color values to keep them within appropriate boundaries, based on default boundaries.

type Device

type Device interface {
	// Target returns the target of this device, usually it's the MAC address.
	Target() Target

	// Dial tries to establish a connection to this device.
	Dial() (net.Conn, error)

	// Source returns a consistent random source to be used with API calls.
	// It's guaranteed to be non-zero.
	Source() uint32

	// NextSequence returns the next sequence value to be used with API calls.
	NextSequence() uint8

	// Send generates and sends a message to the device.
	//
	// conn must be pre-dialed or this function will fail.
	//
	// It calls the device's Target(), Source(), and NextSequence() functions to
	// fill the appropriate headers.
	//
	// The sequence used in this message will be returned.
	Send(ctx context.Context, conn net.Conn, flags AckResFlag, message MessageType, payload interface{}) (seq uint8, err error)

	// SanitizeColor tries to sanitize (keep values inside appropriate boundaries)
	// color based on the device's hardware version, if available.
	//
	// If the device's hardware version was never fetched and cached,
	// it uses default boundaries (see doc for Color.Sanitize).
	SanitizeColor(color Color) Color

	// GetPower returns the current power level of the device.
	//
	// If conn is nil,
	// a new connection will be made and guaranteed to be closed before returning.
	// You should pre-dial and pass in the conn if you plan to call APIs on this
	// device repeatedly.
	GetPower(ctx context.Context, conn net.Conn) (Power, error)
	// SetPower sets the power level of the device.
	// (Turn it on or off.)
	//
	// If conn is nil,
	// a new connection will be made and guaranteed to be closed before returning.
	// You should pre-dial and pass in the conn if you plan to call APIs on this
	// device repeatedly.
	//
	// If ack is false,
	// this function returns nil error after the API is sent successfully.
	// If ack is true,
	// this function will only return nil error after it received ack from the
	// device.
	SetPower(ctx context.Context, conn net.Conn, power Power, ack bool) error

	// The label of the device.
	Label() *Label
	GetLabel(ctx context.Context, conn net.Conn) error

	// The hardware version info of the device.
	HardwareVersion() *HardwareVersion
	GetHardwareVersion(ctx context.Context, conn net.Conn) error
}

Device defines the common interface between lifxlan devices.

For the Foo() and GetFoo() function pairs (e.g. Label() and GetLabel()), the Foo() one will return an pointer to the cached property, guaranteed to be non-nil but could be the zero value, while the GetFoo() one will use an API call to update the cached property.

There will also be an EmptyFoo string constant defined, so that you can compare against Device.Foo().String() to determine if a GetFoo() call is needed. Here is an example code snippet to get a device's label:

func GetLabel(ctx context.Context, d lifxlanDevice) (string, error) {
    if d.Label().String() != lifxlan.EmptyLabel {
        return d.Label().String(), nil
    }
    if err := d.GetLabel(ctx, nil); err = nil {
        return "", nil
    }
    return d.Label().String(), nil
}

If you are extending a device code and you got the property as part of another API's return payload, you can also use the Foo() function to update the cached value. Here is an example code snippet to update a device's cached label:

func UpdateLabel(d lifxlanDevice, newLabel *lifxlan.RawLabel) {
    *d.Label() = *newLabel
}

The conn arg in GetFoo() functions can be nil. In such cases, a new connection will be made and guaranteed to be closed before returning. You should pre-dial and pass in the conn if you plan to call APIs on this device repeatedly.

In case of network error (e.g. response packet loss), the GetFoo() functions might block until the context is cancelled, as a result, it's a good idea to set a timeout to the context.

func NewDevice

func NewDevice(addr string, service ServiceType, target Target) Device

NewDevice creates a new Device.

addr must be in "host:port" format and service must be a known service type, otherwise the later Dial funcion will fail.

type HardwareVersion

type HardwareVersion struct {
	VendorID        uint32
	ProductID       uint32
	HardwareVersion uint32
}

HardwareVersion defines raw version info in message payloads according to:

https://lan.developer.lifx.com/v2.0/docs/device-messages#section-stateversion-33

func (HardwareVersion) Parse

Parse parses the raw hardware version info by looking up ProductMap.

If this hardware version info is not in ProductMap, nil will be returned.

func (HardwareVersion) ProductMapKey

func (raw HardwareVersion) ProductMapKey() uint64

ProductMapKey generates key for ProductMap.

func (HardwareVersion) String

func (raw HardwareVersion) String() string

type Label

type Label [LabelLength]byte

Label defines raw label in message payloads according to:

https://lan.developer.lifx.com/v2.0/docs/device-messages#section-labels

func (Label) Get

func (l Label) Get() interface{}

Get implements flag.Getter interface.

func (*Label) Set

func (l *Label) Set(label string) error

Set encodes label into Label.

Long labels will be truncated. This function always return nil error.

It also implements flag.Value interface.

func (Label) String

func (l Label) String() string

type MessageType

type MessageType uint16

MessageType is the 16-bit header indicates the type of the message.

const (
	Acknowledgement MessageType = 45
	GetService      MessageType = 2
	StateService    MessageType = 3
	GetPower        MessageType = 20
	StatePower      MessageType = 22
	SetPower        MessageType = 21
	GetLabel        MessageType = 23
	StateLabel      MessageType = 25
	GetVersion      MessageType = 32
	StateVersion    MessageType = 33
	StateUnhandled  MessageType = 223
)

MessageType values.

type ParsedHardwareVersion

type ParsedHardwareVersion struct {
	VendorName  string
	ProductName string

	// Features
	Color     bool
	Infrared  bool
	MultiZone bool
	Chain     bool
	Matrix    bool
	// Both values are inclusive.
	MinKelvin uint16
	MaxKelvin uint16

	// Embedded raw info.
	Raw HardwareVersion
}

ParsedHardwareVersion is the parsed hardware version info.

type Power

type Power uint16

Power is the raw power level value in messages.

https://lan.developer.lifx.com/v2.0/docs/device-messages#section-power-level

const (
	PowerOn  Power = 65535
	PowerOff Power = 0
)

Power values.

func (Power) On

func (p Power) On() bool

On returns whether this power level value represents on state.

func (Power) String

func (p Power) String() string

type RawHeader

type RawHeader struct {
	Size   uint16
	Tagged TaggedHeader
	Source uint32
	Target Target

	Flags    AckResFlag
	Sequence uint8

	Type MessageType
	// contains filtered or unexported fields
}

RawHeader defines the struct to be used for encoding and decoding.

https://lan.developer.lifx.com/v2.0/docs/header-description

type RawSetPowerPayload

type RawSetPowerPayload struct {
	Level Power
}

RawSetPowerPayload defines the struct to be used for encoding and decoding.

https://lan.developer.lifx.com/v2.0/docs/device-messages#section-setpower-21

type RawStateLabelPayload

type RawStateLabelPayload struct {
	Label Label
}

RawStateLabelPayload defines the struct to be used for encoding and decoding.

https://lan.developer.lifx.com/v2.0/docs/device-messages#section-statelabel-25

type RawStatePowerPayload

type RawStatePowerPayload struct {
	Level Power
}

RawStatePowerPayload defines the struct to be used for encoding and decoding.

https://lan.developer.lifx.com/v2.0/docs/device-messages#section-statepower-22

type RawStateServicePayload

type RawStateServicePayload struct {
	Service ServiceType
	Port    uint32
}

RawStateServicePayload defines the struct to be used for encoding and decoding.

https://lan.developer.lifx.com/v2.0/docs/device-messages#section-stateservice-3

type RawStateUnhandledPayload added in v0.1.3

type RawStateUnhandledPayload struct {
	UnhandledType MessageType
}

RawStateUnhandledPayload defines the struct to be used for encoding and decoding.

https://lan.developer.lifx.com/v2.0/docs/the-lifx-switch#section-stateunhandled-223

func (RawStateUnhandledPayload) GenerateError added in v0.1.3

func (p RawStateUnhandledPayload) GenerateError(expected MessageType) error

GenerateError generates an error regarding this unhandled message.

type RawStateVersionPayload

type RawStateVersionPayload struct {
	Version HardwareVersion
}

RawStateVersionPayload defines the struct to be used for encoding and decoding.

https://lan.developer.lifx.com/v2.0/docs/device-messages#section-stateversion-33

type Response

type Response struct {
	Message  MessageType
	Flags    AckResFlag
	Source   uint32
	Target   Target
	Sequence uint8
	Payload  []byte
}

Response is the parsed response from a lifxlan device.

func ParseResponse

func ParseResponse(msg []byte) (*Response, error)

ParseResponse parses the response received from a lifxlan device.

func ReadNextResponse

func ReadNextResponse(ctx context.Context, conn net.Conn) (*Response, error)

ReadNextResponse returns the next received response.

It handles read buffer, deadline, context cancellation check, and response parsing.

type ServiceType

type ServiceType uint8

ServiceType define the type of the service this device provides.

const (
	ServiceUDP ServiceType = 1
)

Documented values for ServiceType.

func (ServiceType) String

func (s ServiceType) String() string

type TaggedHeader

type TaggedHeader uint16

TaggedHeader is the 16-bit header including:

- origin: 2 bits, must be 0

- tagged: 1 bit

- addressable: 1 bit, must be 1

- protocol: 12 bits, must be 1024

const (
	NotTagged TaggedHeader = 1<<12 + 1024
	Tagged    TaggedHeader = 1<<13 + NotTagged
)

Tagged and non-tagged versions of TaggedHeader.

type Target

type Target uint64

Target defines a target by its MAC address.

const AllDevices Target = 0

AllDevices is the special Target value means all devices.

func ParseTarget

func ParseTarget(s string) (t Target, err error)

ParseTarget parses s into a Target.

s should be in the format of a MAC address, e.g. "01:23:45:67:89:ab", or the special values for AllDevices: "00:00:00:00:00:00" and "".

func (Target) Get

func (t Target) Get() interface{}

Get implements flag.Getter interface.

func (Target) Matches

func (t Target) Matches(other Target) bool

Matches returns true if either target is AllDevices, or both targets have the same value.

func (*Target) Set

func (t *Target) Set(s string) (err error)

Set implements flag.Value interface.

It calls ParseTarget to parse the string. Refer to the doc of ParseTarget for more details.

Example
package main

import (
	"flag"

	"github.com/fishy/lifxlan"
)

func main() {
	var target lifxlan.Target
	flag.Var(
		&target,
		"target",
		"The MAC address of the target device. Empty value means any device.",
	)
	flag.Parse()
}
Output:

func (Target) String

func (t Target) String() string

type Timestamp

type Timestamp uint64

Timestamp is the type used in messages to represent a timestamp.

It's defined as nanoseconds since UNIX EPOCH.

func ConvertTime

func ConvertTime(t time.Time) Timestamp

ConvertTime converts a time.Time into Timestamp.

func (Timestamp) String

func (ts Timestamp) String() string

func (Timestamp) Time

func (ts Timestamp) Time() time.Time

Time converts a Timestamp into time.Time.

type TransitionTime

type TransitionTime uint32

TransitionTime is the type used in messages to represent transition time.

Its unit is milliseconds.

func ConvertDuration

func ConvertDuration(d time.Duration) TransitionTime

ConvertDuration converts a time.Duration into TransitionTime.

The max uint32 value can represent a transition time of more than 1,193 hours[1] (or, in other words, more than a month). So although an overflow is technically possible, we don't really do any special handlings here (it's not a security risk and won't crash anything[2]). If you feed in a duration that overflows TransitionTime, you should feel bad (or great, it's totally up to you) about it. Do you really want your light(s) to take more than a month to turn on/off?

[1] https://play.golang.com/p/LqfMpvhIctx

[2] https://play.golang.com/p/edwqG4nNqkt

func (TransitionTime) Duration

func (tt TransitionTime) Duration() time.Duration

Duration converts a TransitionTime into time.Duration.

func (TransitionTime) String

func (tt TransitionTime) String() string

type WaitForAcksError

type WaitForAcksError struct {
	Received []uint8
	Total    []uint8
	Cause    error
}

WaitForAcksError defines the error returned by WaitForAcks.

func (*WaitForAcksError) Error

func (e *WaitForAcksError) Error() string

func (*WaitForAcksError) Unwrap added in v0.1.1

func (e *WaitForAcksError) Unwrap() error

Unwrap returns the underlying error.

Directories

Path Synopsis
cmd
gen-product-map
Command gen-product-map is the helper tool to generate lifxlan ProductMap.
Command gen-product-map is the helper tool to generate lifxlan ProductMap.
Package light implements LIFX LAN Protocol for LIFX light devices: https://lan.developer.lifx.com/v2.0/docs/light-messages Please refer to its parent package for more background/context.
Package light implements LIFX LAN Protocol for LIFX light devices: https://lan.developer.lifx.com/v2.0/docs/light-messages Please refer to its parent package for more background/context.
Package mock implements a mocked lifxlan device listening on localhost, which can be used in test code to test API calls.
Package mock implements a mocked lifxlan device listening on localhost, which can be used in test code to test API calls.
Package tile implements LIFX LAN Protocol for LIFX Tile devices: https://lan.developer.lifx.com/v2.0/docs/tile-control A tile device is also a light device and implements all light APIs.
Package tile implements LIFX LAN Protocol for LIFX Tile devices: https://lan.developer.lifx.com/v2.0/docs/tile-control A tile device is also a light device and implements all light APIs.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL