AlexxIT/go2rtc · error

failed to decode hex

Error message

failed to decode hex: %w

What it means

WakeUp builds a binary payload by decoding the hex string two characters at a time with hex.DecodeString; this error wraps any hex decoding failure. It means the hex string passed to WakeUp is malformed — odd length or containing non-hex characters. The message is never published in this case.

Solutions

  1. Validate the hex string length is even before calling WakeUp
  2. Validate all characters are valid hex (regexp ^[0-9a-fA-F]+$)
  3. Check where the hex string comes from (config/env/DB) for truncation or encoding mistakes
  4. If the source is base64, decode with base64.StdEncoding instead of hex

Example fix

// before
err := client.WakeUp(devKey)
// after
var re = regexp.MustCompile(`^[0-9a-fA-F]+$`)
if len(devKey)%2 != 0 || !re.MatchString(devKey) {
    return fmt.Errorf("invalid hex key: %q", devKey)
}
err := client.WakeUp(devKey)
Defensive patterns

Strategy: validation

Validate before calling

var hexRe = regexp.MustCompile(`^[0-9a-fA-F]+$`)
func validHex(s string) bool { return len(s)%2 == 0 && hexRe.MatchString(s) }

Type guard

func isHexString(s string) bool {
    if len(s) == 0 || len(s)%2 != 0 { return false }
    _, err := hex.DecodeString(s)
    return err == nil
}

Try / catch

if !isHexString(key) { return fmt.Errorf("not a valid hex key: %q", key) }
if err := client.WakeUp(key); err != nil {
    var hexErr *hex.InvalidByteError
    if errors.As(err, &hexErr) { /* malformed input, do not retry */ }
}

Prevention

When it happens

Trigger: Calling WakeUp with a deviceId-adjacent hex string argument that has an odd number of characters or contains characters outside [0-9a-fA-F]; hex.DecodeString fails on one of the 2-char slices.

Common situations: Happens when a device key or payload was copied with extra whitespace/quotes, when a base64 string is mistakenly passed where hex is expected, or when a truncated key (odd length) is loaded from config.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of AlexxIT/go2rtc@c245815e75 (2026-09-07). Data as JSON: /api/errors/bdd03cd6a5960c60. Report an issue: GitHub.

Appendix: source

Thrown at pkg/tuya/mqtt.go:183

	c.closed = true
}

// WakeUp sends a wake-up signal to battery-powered cameras (LowPower mode).
// The camera wakes up and starts responding immediately - we don't wait for dps[149].
// Note: LowPower cameras sleep after ~3 minutes of inactivity.
func (c *TuyaMqttClient) WakeUp(localKey string) error {
	// Calculate CRC32 of localKey as wake-up payload
	crc := crc32.ChecksumIEEE([]byte(localKey))

	// Convert to hex string
	hexStr := fmt.Sprintf("%08x", crc)

	// Convert hex string to byte array (2 chars at a time)
	payload := make([]byte, len(hexStr)/2)
	for i := 0; i < len(hexStr); i += 2 {
		b, err := hex.DecodeString(hexStr[i : i+2])
		if err != nil {
			return fmt.Errorf("failed to decode hex: %w", err)
		}
		payload[i/2] = b[0]
	}

	// Publish to wake-up topic: m/w/{deviceId}
	wakeUpTopic := fmt.Sprintf("m/w/%s", c.deviceId)
	token := c.client.Publish(wakeUpTopic, 1, false, payload)
	if token.Wait() && token.Error() != nil {
		return fmt.Errorf("failed to publish wake-up message: %w", token.Error())
	}

	// Subscribe to lowPower topic to receive dps[149] status updates
	// (we don't wait for this signal - camera responds immediately)
	lowPowerTopic := fmt.Sprintf("smart/decrypt/in/%s", c.deviceId)
	if token := c.client.Subscribe(lowPowerTopic, 1, c.onLowPowerMessage); token.Wait() && token.Error() != nil {
		return fmt.Errorf("failed to subscribe to lowPower topic: %w", token.Error())
	}

View on GitHub (pinned to c245815e75)