quote0

package module
v0.0.0-...-d2c2fac Latest Latest
Warning

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

Go to latest
Published: Nov 14, 2025 License: MIT Imports: 15 Imported by: 0

README

Quote/0 Go SDK

Go Reference Go Report Card Go Version License

A Go SDK and CLI for Quote/0, a Wi-Fi enabled e-ink display device with a 296×152 pixel screen. This library provides a type-safe interface to the official Text and Image APIs, handling Bearer authentication, rate limiting (1 QPS), and error normalization for both JSON and plain-text responses. Built using only the Go standard library.

Requirements: Go 1.18+

API Documentation:

Features

  • Bearer auth with default device ID and per-request overrides
  • 1 QPS rate limiter (context-aware, pluggable)
  • Robust error normalization for JSON or plain-text responses
  • Idiomatic, well-documented Go API with convenience helpers
  • No third-party dependencies (Go stdlib only)
  • Minimal CLI for quick sends and smoke tests

Installation

As a library:

go get github.com/1set/quote0

Build the CLI:

go build ./cmd/quote0

Quick Start

package main

import (
    "context"
    "log"
    "os"
    "time"

    "github.com/1set/quote0"
)

func main() {
    client, err := quote0.NewClient(
        os.Getenv("QUOTE0_TOKEN"),
        quote0.WithDefaultDeviceID(os.Getenv("QUOTE0_DEVICE")),
    )
    if err != nil { log.Fatal(err) }

    // Send text (all fields are optional except DeviceID which can be set via client default)
    _, err = client.SendText(context.Background(), quote0.TextRequest{
        RefreshNow: quote0.Bool(true),
        Title:      "Status Update",
        Message:    "Deployment finished successfully (OK)",
        Signature:  time.Now().Format("2006-01-02 15:04:05 MST"),
    })
    if err != nil { log.Fatal(err) }
}

API Overview

Create a client:

  • NewClient(apiToken string, opts ...ClientOption) (*Client, error)

Client options:

  • WithDefaultDeviceID(deviceID string) - set default device ID
  • WithBaseURL(baseURL string) - override host (defaults to https://dot.mindreset.tech)
  • WithHTTPClient(*http.Client) - custom HTTP client
  • WithRateLimiter(RateLimiter) - custom limiter (nil disables client-side limiting)
  • WithUserAgent(string) - custom User-Agent (empty string sends empty UA; omit to use SDK default)
  • WithDebug(bool) - enable debug mode to log request/response details to stderr
Text API
  • SendText(ctx context.Context, req TextRequest) (*APIResponse, error)
  • SendTextToDevice(ctx, deviceID string, req TextRequest) (*APIResponse, error)
  • SendTextSimple(title, message string, signature ...string) (*APIResponse, error)

TextRequest fields:

  • RefreshNow (optional bool pointer) - immediate refresh
  • DeviceID - filled automatically from default if omitted (required)
  • Title - optional; displays on the first line
  • Message - optional; displays on the next three lines
  • Signature - optional; displays fixed at the bottom-right corner
  • Icon - optional base64 40x40 px PNG shown at the bottom-left corner
  • Link - optional URL opened via the Dot app

Display Layout: The Quote/0 screen has a fixed layout for Text API mode. Title appears on the first line, followed by message text spanning three lines. Icon (if provided) appears at the bottom-left corner, and signature at the bottom-right. If any field is omitted, that area remains blank - the layout does not reflow or adjust responsively.

Note: All fields except DeviceID are optional. You can send a text request with only DeviceID to refresh the display without changing content.

Image API
  • SendImage(ctx context.Context, req ImageRequest) (*APIResponse, error)
  • SendImageToDevice(ctx, deviceID string, req ImageRequest) (*APIResponse, error)
  • SendImageSimple(base64PNG string) (*APIResponse, error)

In addition to sending a base64 string, the SDK can encode for you:

  • Provide raw bytes: set ImageBytes in ImageRequest, or call SendImageBytes(ctx, png, req).
  • Provide a file path: set ImagePath in ImageRequest, or call SendImageFile(ctx, path, req).

ImageRequest fields:

  • Image - base64 296x152 px PNG (required unless ImageBytes or ImagePath is provided)
  • ImageBytes - raw 296x152 px PNG bytes; SDK encodes to base64 (json:"-")
  • ImagePath - path to a 296x152 px PNG file; SDK reads + encodes (json:"-")
  • Link - optional URL
  • Border - optional screen edge color: BorderWhite (default) or BorderBlack
  • DitherType, DitherKernel - optional dithering parameters

Note: If ditherType is omitted, the server defaults to error diffusion with the Floyd-Steinberg kernel (equivalent to ditherType=DIFFUSION + ditherKernel=FLOYD_STEINBERG).

Important: ditherKernel is only effective when ditherType=DIFFUSION. When ditherType is ORDERED or NONE, the kernel parameter is ignored by the server.

Supported values:

  • ditherType: DIFFUSION | ORDERED | NONE
  • ditherKernel (only for DIFFUSION): FLOYD_STEINBERG | ATKINSON | BURKES | SIERRA2 | STUCKI | JARVIS_JUDICE_NINKE | DIFFUSION_ROW | DIFFUSION_COLUMN | DIFFUSION_2D | THRESHOLD

Quick guidance:

  • DIFFUSION with various kernels - error diffusion creates natural gradients. Default kernel is FLOYD_STEINBERG (balanced). Other kernels like ATKINSON (crisp), STUCKI (sharp), JARVIS_JUDICE_NINKE (smooth) offer different visual characteristics.
  • ORDERED - fixed halftone pattern; may show grid artifacts in gradients. Kernel parameter has no effect.
  • NONE - no dithering; recommended for text-based images. Kernel parameter has no effect.

Example with border:

client.SendImage(ctx, quote0.ImageRequest{
    RefreshNow: quote0.Bool(true),
    Image:      base64EncodedPNG,
    Border:     quote0.BorderBlack,  // or quote0.BorderWhite (default)
})
Error Handling

All non-2xx responses return *quote0.APIError:

resp, err := client.SendText(ctx, req)
if err != nil {
    if apiErr, ok := err.(*quote0.APIError); ok {
        // Status code + normalized message
        log.Printf("API error: status=%d code=%s msg=%s", apiErr.StatusCode, apiErr.Code, apiErr.Message)
        if quote0.IsRateLimitError(err) { log.Print("rate limited") }
        if quote0.IsAuthError(err) { log.Print("auth error") }
    } else {
        log.Fatalf("network error: %v", err)
    }
}

The SDK accepts both JSON error envelopes and plain-text (including Chinese) messages.

Rate Limit

The built-in limiter enforces 1 QPS across the client. For advanced control:

client, _ := quote0.NewClient(
    token,
    quote0.WithRateLimiter(quote0.NewFixedIntervalLimiter(1500*time.Millisecond)),
)
Debug Mode

Enable debug mode to log HTTP request/response details to stderr for troubleshooting:

client, _ := quote0.NewClient(token, quote0.WithDebug(true))

Logs include timestamps, headers (token masked), body, and elapsed time. CLI also supports -debug flag.

CLI Usage

Build:

go build -o quote0 ./cmd/quote0

Environment defaults:

  • QUOTE0_TOKEN - API token
  • QUOTE0_DEVICE - default device ID

Send text:

QUOTE0_TOKEN=YOUR_DOT_APP_TOKEN QUOTE0_DEVICE=YOUR_DEVICE_ID \
  ./quote0 text -title "Hello" -message "World" -signature "2025-11-08 14:00 CST"

Send image from file or base64:

./quote0 image -token "$QUOTE0_TOKEN" -device "$QUOTE0_DEVICE" -image-file screen.png
./quote0 image -token "$QUOTE0_TOKEN" -device "$QUOTE0_DEVICE" -image "<base64>"

Enable debug mode to see request/response details:

./quote0 text -title "Debug" -message "Test" -debug

All commands support -debug flag to log HTTP request and response details to stderr for troubleshooting.

Notes & Limits

  • Endpoints:
    • Text: POST https://dot.mindreset.tech/api/open/text
    • Image: POST https://dot.mindreset.tech/api/open/image
  • API rate limit: 1 QPS. The client enforces this by default.
  • This SDK is stdlib-only to keep builds simple and portable.

Documentation

Overview

Package quote0 provides a Go 1.18+ SDK for Quote/0 e-ink display devices.

Quote/0 is a Wi-Fi enabled e-paper display with a 296×152 pixel screen that receives content updates via REST API. The device maintains displayed content without power (bistable e-ink) and supports both text-based layouts and arbitrary image rendering. See https://dot.mindreset.tech for device specifications and documentation.

Features

  • Bearer token authentication
  • Optional default device ID with per-request override
  • 1 QPS rate limiting (pluggable, context aware)
  • Robust error handling for JSON and plain-text (Chinese) responses
  • No third-party dependencies (stdlib only)

Official API Documentation:

Index

Constants

View Source
const (
	// DefaultBaseURL is the default API host. Endpoints are under /api/open/*.
	// This matches the official curl examples.
	DefaultBaseURL = "https://dot.mindreset.tech"
)

Variables

View Source
var (
	// ErrDeviceIDMissing indicates deviceId is required.
	ErrDeviceIDMissing = errors.New("quote0: deviceId is required")
	// ErrImagePayloadMissing indicates image payload is required.
	ErrImagePayloadMissing = errors.New("quote0: image payload is required")
	// ErrTitleMissing indicates title is required.
	ErrTitleMissing = errors.New("quote0: title is required")
	// ErrMessageMissing indicates message is required.
	ErrMessageMissing = errors.New("quote0: message is required")
)

Functions

func Bool

func Bool(v bool) *bool

Bool returns a pointer to a bool.

func Int

func Int(v int) *int

Int returns a pointer to an int.

func IsAuthError

func IsAuthError(err error) bool

IsAuthError returns true if err is an APIError with HTTP status 401 or 403 (authentication/authorization failure).

func IsRateLimitError

func IsRateLimitError(err error) bool

IsRateLimitError returns true if err is an APIError with HTTP status 429 (Too Many Requests).

Types

type APIError

type APIError struct {
	StatusCode int
	// Code is a normalized string representation of server error code when present (e.g. "429").
	Code string
	// Message is a human-readable message from the server or synthesized from body.
	Message string
	// RawBody keeps the original payload for debugging.
	RawBody []byte
}

APIError captures non-2xx responses. The service may return JSON or plain text (e.g. Chinese).

func (*APIError) Error

func (e *APIError) Error() string

type APIResponse

type APIResponse struct {
	// Code carries the numeric status returned by the Quote/0 API (0 on success).
	Code int `json:"code"`
	// Message is the string message provided by the service (e.g., "ok" or reason text).
	Message string `json:"message"`
	// Result contains the raw JSON payload (varies per endpoint; caller can unmarshal).
	Result json.RawMessage `json:"result"`
	// StatusCode keeps the HTTP status code observed for the request.
	StatusCode int `json:"-"`
	// RawBody contains the exact response bytes for troubleshooting or custom parsing.
	RawBody []byte `json:"-"`
}

APIResponse reflects a typical JSON envelope from the service. Some responses may be plain text; in those cases, Message/StatusCode/RawBody are set.

type BorderColor

type BorderColor int

BorderColor controls the screen edge color on the Quote/0 display.

const (
	// BorderWhite renders a white border around the display (default).
	BorderWhite BorderColor = 0
	// BorderBlack renders a black border around the display.
	BorderBlack BorderColor = 1
)

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client exposes the Quote/0 APIs with proper authentication and rate limiting.

func NewClient

func NewClient(apiKey string, opts ...ClientOption) (*Client, error)

NewClient builds a client. apiKey is required (format: dot_app_xxx).

func (*Client) GetDefaultDeviceID

func (c *Client) GetDefaultDeviceID() string

GetDefaultDeviceID returns the current default device ID.

func (*Client) SendImage

func (c *Client) SendImage(ctx context.Context, payload ImageRequest) (*APIResponse, error)

SendImage uploads a base64-encoded image to the device. If DeviceID is empty, the client's default device is used.

func (*Client) SendImageBytes

func (c *Client) SendImageBytes(ctx context.Context, png []byte, meta ImageRequest) (*APIResponse, error)

SendImageBytes is a convenience that accepts raw PNG bytes and performs base64 encoding internally.

func (*Client) SendImageFile

func (c *Client) SendImageFile(ctx context.Context, path string, meta ImageRequest) (*APIResponse, error)

SendImageFile is a convenience that accepts a PNG file path and performs base64 encoding internally.

func (*Client) SendImageSimple

func (c *Client) SendImageSimple(base64PNG string) (*APIResponse, error)

SendImageSimple sends an image with default device and immediate refresh using Background context.

func (*Client) SendImageToDevice

func (c *Client) SendImageToDevice(ctx context.Context, deviceID string, payload ImageRequest) (*APIResponse, error)

SendImageToDevice is a convenience to target a specific device.

func (*Client) SendText

func (c *Client) SendText(ctx context.Context, payload TextRequest) (*APIResponse, error)

SendText sends text content. If DeviceID is empty, the client's default device is used.

func (*Client) SendTextSimple

func (c *Client) SendTextSimple(title, message string, signature ...string) (*APIResponse, error)

SendTextSimple is a convenience helper using Background context and immediate refresh. Title and message are optional. Signature is variadic; when omitted, no signature is sent.

func (*Client) SendTextToDevice

func (c *Client) SendTextToDevice(ctx context.Context, deviceID string, payload TextRequest) (*APIResponse, error)

SendTextToDevice is a convenience to target a specific device.

func (*Client) SetDefaultDeviceID

func (c *Client) SetDefaultDeviceID(deviceID string)

SetDefaultDeviceID updates the default device ID in a thread-safe manner.

type ClientOption

type ClientOption func(*Client)

ClientOption mutates the client during construction.

func WithBaseURL

func WithBaseURL(baseURL string) ClientOption

WithBaseURL overrides the API host (useful for staging/tests). No trailing slash required.

func WithDebug

func WithDebug(debug bool) ClientOption

WithDebug enables debug mode which logs request details (method, URL, headers, body) to stderr. Useful for debugging and verifying SDK behavior.

func WithDefaultDeviceID

func WithDefaultDeviceID(deviceID string) ClientOption

WithDefaultDeviceID sets a default device serial number used when request omits deviceId.

func WithHTTPClient

func WithHTTPClient(hc *http.Client) ClientOption

WithHTTPClient installs a custom http.Client.

func WithRateLimiter

func WithRateLimiter(l RateLimiter) ClientOption

WithRateLimiter replaces the default limiter. Pass nil to disable (not recommended).

func WithUserAgent

func WithUserAgent(ua string) ClientOption

WithUserAgent sets a custom User-Agent string. Pass an empty string to send an empty User-Agent header (not recommended). If not called, the client uses a default SDK User-Agent.

type DitherKernel

type DitherKernel string

DitherKernel enumerates supported dithering kernels/algorithms.

IMPORTANT: DitherKernel is only effective when DitherType is set to DIFFUSION. When DitherType is ORDERED or NONE, the kernel parameter is ignored by the server.

When DitherType is DIFFUSION, these kernels control how quantization error is spread: - KernelFloydSteinberg: Classic 3x3 diffusion (default). Balanced detail and smoothness. - KernelAtkinson: Lighter diffusion footprint; preserves micro-detail and text; slightly lighter images. - KernelBurkes: Row-oriented diffusion similar to Stucki but lighter weights; sharp edges, fine detail. - KernelSierra2: Sierra-2 variant; smooth gradients with moderate grain; balanced look. - KernelStucki: Larger footprint; strong contrast and crisp edges; can introduce more visible grain. - KernelJarvisJudiceNinke: Large footprint; very smooth gradients, may blur fine details slightly. - KernelDiffusionRow: Directional diffusion along rows; preserves horizontal detail; may create row texture. - KernelDiffusionColumn: Directional diffusion along columns; preserves vertical detail; may create column texture. - KernelDiffusion2D: More isotropic spread across 2D neighborhood; balances artifacts across directions. - KernelThreshold: Listed for completeness, but primarily used with ORDERED dither type.

const (
	// KernelThreshold applies ordered dithering threshold matrix; no error diffusion; strong halftone look.
	KernelThreshold DitherKernel = "THRESHOLD"
	// KernelAtkinson uses compact diffusion pattern; good for text and small features; lighter tones.
	KernelAtkinson DitherKernel = "ATKINSON"
	// KernelBurkes applies diffusion with emphasis on immediate neighbors along the row; sharp, detailed output.
	KernelBurkes DitherKernel = "BURKES"
	// KernelFloydSteinberg is the default, classic diffusion; natural gradients and balanced detail/grain.
	KernelFloydSteinberg DitherKernel = "FLOYD_STEINBERG"
	// KernelSierra2 applies Sierra-2 diffusion; smooth gradients; moderate granularity.
	KernelSierra2 DitherKernel = "SIERRA2"
	// KernelStucki uses larger kernel; increases perceived sharpness; can be grainier.
	KernelStucki DitherKernel = "STUCKI"
	// KernelJarvisJudiceNinke produces very smooth gradients due to large kernel; may soften fine details.
	KernelJarvisJudiceNinke DitherKernel = "JARVIS_JUDICE_NINKE"
	// KernelDiffusionRow applies directional diffusion horizontally; preserves horizontal features.
	KernelDiffusionRow DitherKernel = "DIFFUSION_ROW"
	// KernelDiffusionColumn applies directional diffusion vertically; preserves vertical features.
	KernelDiffusionColumn DitherKernel = "DIFFUSION_COLUMN"
	// KernelDiffusion2D spreads error across a 2D neighborhood; balances directional artifacts.
	KernelDiffusion2D DitherKernel = "DIFFUSION_2D"
)

type DitherType

type DitherType string

DitherType enumerates server-accepted dithering modes.

Server behavior (per official docs):

  • If ditherType is omitted, the default is error diffusion using the Floyd-Steinberg kernel.
  • DIFFUSION: Error diffusion algorithms that distribute quantization errors to neighbors. When using DIFFUSION, you can specify a DitherKernel to control the error distribution pattern.
  • ORDERED: Fixed periodic threshold matrix producing uniform dot patterns with regular grids.
  • NONE: Disables dithering entirely; recommended for text-based images for sharper rendering.

Note: DitherKernel is only effective when DitherType is DIFFUSION. For ORDERED or NONE, the kernel parameter is ignored by the server.

const (
	// DitherNone disables dithering entirely; pixels are binarized by a simple threshold.
	// Suitable for high-contrast line art or when you require crisp edges without added grain,
	// but gradients will posterize.
	DitherNone DitherType = "NONE"
	// DitherDiffusion enables error-diffusion dithering. Each pixel's quantization error
	// is distributed to neighbors based on a selected kernel, producing smooth tonal
	// transitions with natural-looking noise. Good general-purpose choice for photos/text.
	DitherDiffusion DitherType = "DIFFUSION"
	// DitherOrdered enables ordered dithering via a threshold matrix (Bayer-like). This
	// yields a regular halftone pattern that preserves uniform regions and text edges well,
	// but can reveal grid artifacts in smooth gradients.
	DitherOrdered DitherType = "ORDERED"
)

type ImageRequest

type ImageRequest struct {
	// RefreshNow toggles an immediate display refresh for the targeted screen.
	RefreshNow *bool `json:"refreshNow,omitempty"`
	// DeviceID is the Quote/0 serial number (hexadecimal string). Leave empty to use the client's default device.
	DeviceID string `json:"deviceId"`
	// Image is a base64-encoded 296x152px PNG payload as required by the server.
	// You normally do not need to set this directly when using ImageBytes or ImagePath.
	Image string `json:"image"`
	// ImageBytes allows providing raw 296x152px PNG bytes; the SDK will base64-encode internally.
	ImageBytes []byte `json:"-"`
	// ImagePath allows providing a file path to a 296x152px PNG; the SDK will read and base64-encode internally.
	ImagePath string `json:"-"`
	// Link is an optional URL opened inside the Quote/0 companion app.
	Link string `json:"link,omitempty"`
	// Border selects the screen edge color. Use BorderWhite (default) or BorderBlack.
	Border BorderColor `json:"border,omitempty"`
	// DitherType selects the server-side dithering strategy for tone reproduction.
	DitherType DitherType `json:"ditherType,omitempty"`
	// DitherKernel narrows the algorithm used when DitherType=DIFFUSION or ORDERED.
	DitherKernel DitherKernel `json:"ditherKernel,omitempty"`
}

ImageRequest matches the /api/open/image payload.

type RateLimiter

type RateLimiter interface {
	Wait(ctx context.Context) error
}

RateLimiter gates outgoing API calls so we stay under the documented 1 QPS. Implementations must honor context cancellation so callers can abort pending calls cleanly.

func NewFixedIntervalLimiter

func NewFixedIntervalLimiter(interval time.Duration) RateLimiter

NewFixedIntervalLimiter creates a simple, concurrency-safe limiter that enforces a minimum interval between requests. It is intentionally lightweight (mutex+timer) so it can run inside tiny IoT gateways or embedded controllers without extra deps. Use this for the official 1 QPS policy by passing time.Second, or customize as needed.

type RateLimiterFunc

type RateLimiterFunc func(ctx context.Context) error

RateLimiterFunc adapts a function into a RateLimiter.

func (RateLimiterFunc) Wait

func (f RateLimiterFunc) Wait(ctx context.Context) error

Wait implements the RateLimiter interface by invoking the underlying function.

type TextRequest

type TextRequest struct {
	// RefreshNow toggles an immediate refresh on the targeted Quote/0 display. Optional.
	RefreshNow *bool `json:"refreshNow,omitempty"`
	// DeviceID is the Quote/0 serial number (hexadecimal string). Required. Leave empty to use the client's default.
	DeviceID string `json:"deviceId"`
	// Title displays on the first line. Optional.
	Title string `json:"title,omitempty"`
	// Message displays on the next three lines. Optional.
	Message string `json:"message,omitempty"`
	// Signature displays fixed at the bottom-right corner. Optional.
	Signature string `json:"signature,omitempty"`
	// Icon is a base64-encoded 40x40px PNG shown at the bottom-left corner. Optional.
	Icon string `json:"icon,omitempty"`
	// Link is an optional URL that the Quote/0 companion app can open when interacting with the device.
	Link string `json:"link,omitempty"`
}

TextRequest matches the /api/open/text payload. Only DeviceID is required; all other fields are optional.

Display Layout: The Quote/0 screen has a fixed layout (296x152 pixels):

  • Title: displays on the first line
  • Message: displays on the next three lines
  • Icon: 40x40px at the bottom-left corner
  • Signature: fixed at the bottom-right corner

If any field is omitted, that area remains blank. The layout does not reflow or adjust responsively.

Directories

Path Synopsis
cmd
quote0 command
Package main provides a simple CLI for testing the Quote/0 SDK.
Package main provides a simple CLI for testing the Quote/0 SDK.

Jump to

Keyboard shortcuts

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