Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
207 changes: 207 additions & 0 deletions display/displaytest/text.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
// Copyright 2024 The Periph Authors. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.

package displaytest

import (
"errors"
"fmt"
"time"

"periph.io/x/conn/v3/display"
)

// TestTextDisplay exercises the methods provided by the interface. It can be
// used interactively as a quick smoke test of an implementation, and from test
// routines. This doesn't test brightness or contrast to avoid EEPROM wear
// issues.
func TestTextDisplay(dev display.TextDisplay, interactive bool) []error {
var result []error
var err error

pauseTime := time.Duration(0)
if interactive {
pauseTime = 3 * time.Second
}
// Turn the dev on and write the String() value.
if err = dev.Display(true); err != nil {
result = append(result, err)
}

if err = dev.Clear(); err != nil {
result = append(result, err)
}

if _, err = dev.WriteString(dev.String()); err != nil {
result = append(result, err)
}
time.Sleep(pauseTime)

if err = dev.Clear(); err != nil {
result = append(result, err)
}
_, err = dev.WriteString("Auto Scroll Test")
if err != nil {
result = append(result, err)
}
time.Sleep(pauseTime)
err = dev.AutoScroll(true)
if err != nil {
result = append(result, err)
}
// Test Display fill
for line := range dev.Rows() {
c := rune('A')
if err = dev.MoveTo(dev.MinRow()+line, dev.MinCol()); err != nil {
result = append(result, err)
}
for col := range dev.Cols() {
if col%5 == 0 && col > 0 {
_, err = dev.Write([]byte{byte(' ')})
} else {
_, err = dev.Write([]byte{byte(c)})
}
if err != nil {
result = append(result, err)
}
c = c + 1
}
}
// Test AutoScroll working
time.Sleep(pauseTime)
nWritten, err := dev.WriteString("auto scroll happen")
if err != nil {
result = append(result, err)
}
if nWritten != 18 {
result = append(result, fmt.Errorf("dev.WriteString() expected %d chars written, received %d", 18, nWritten))
}
time.Sleep(pauseTime)
if err = dev.AutoScroll(false); err != nil {
result = append(result, err)
}
time.Sleep(pauseTime)

// Test Absolute Positioning
if err = dev.Clear(); err != nil {
result = append(result, err)
}
if _, err = dev.WriteString("Absolute Positioning"); err != nil {
result = append(result, err)
}
time.Sleep(pauseTime)
if err = dev.Clear(); err != nil {
result = append(result, err)
}
for ix := range dev.Rows() {
if err = dev.MoveTo(dev.MinRow()+ix, dev.MinCol()+ix); err != nil {
result = append(result, err)
}
if _, err = dev.WriteString(fmt.Sprintf("(%d,%d)", dev.MinRow()+ix, dev.MinCol()+ix)); err != nil {
result = append(result, err)
}
}
time.Sleep(pauseTime)

// Test that MoveTo returns error for invalid coordinates
moveCases := []struct {
row int
col int
}{
{row: dev.MinRow() - 1, col: dev.MinCol()},
{row: dev.MinRow(), col: dev.MinCol() - 1},
{row: dev.Rows() + 1, col: dev.Cols()},
{row: dev.Rows(), col: dev.Cols() + 1},
}
for _, tc := range moveCases {
if err = dev.MoveTo(tc.row, tc.col); err == nil {
result = append(result, fmt.Errorf("did not receive expected error on MoveTo(%d,%d)", tc.row, tc.col))
}
}

// Test Cursor Modes
if err = dev.Clear(); err != nil {
result = append(result, err)
}
modes := []string{"Off", "Underline", "Block", "Blink"}
for ix := display.CursorOff; ix <= display.CursorBlink; ix++ {
if err = dev.MoveTo(dev.MinRow()/2+1, dev.MinCol()); err != nil {
result = append(result, err)
}
if _, err = dev.WriteString("Cursor: " + modes[ix]); err != nil {
result = append(result, err)
}
if err = dev.Cursor(ix); err != nil {
result = append(result, err)
}
time.Sleep(pauseTime)
if err = dev.Cursor(display.CursorOff); err != nil {
result = append(result, err)
}
if err = dev.Clear(); err != nil {
result = append(result, err)
}
}
if err = dev.Cursor(display.CursorBlink + 1); err == nil {
result = append(result, errors.New("did not receive expected error on Cursor() with invalid value"))
}

// Test Move Forward and Backward. 2 Should overwrite the 1
if err = dev.Clear(); err != nil {
result = append(result, err)
}
if _, err = dev.WriteString("Testing >"); err != nil {
result = append(result, err)
}
if err = dev.Move(display.Forward); err != nil {
result = append(result, err)
}
if err = dev.Move(display.Forward); err != nil {
result = append(result, err)
}
for ix := range 10 {
if _, err = dev.WriteString(fmt.Sprintf("%d", ix)); err != nil {
result = append(result, err)
}
time.Sleep(pauseTime)
if err = dev.Move(display.Backward); err != nil {
result = append(result, err)
}
}
if err = dev.Move(display.Down + 1); err == nil {
result = append(result, errors.New("did not receive expected error on Move() with invalid value"))
}

// Test Display on/off
if err = dev.Clear(); err != nil {
result = append(result, err)
}
if _, err = dev.WriteString("Set dev off"); err != nil {
result = append(result, err)
}
time.Sleep(pauseTime)
if err = dev.Display(false); err != nil {
result = append(result, err)
}
time.Sleep(pauseTime)
if err = dev.Display(true); err != nil {
result = append(result, err)
}
if err = dev.Clear(); err != nil {
result = append(result, err)
}
if _, err = dev.WriteString("Set dev on"); err != nil {
result = append(result, err)
}
time.Sleep(pauseTime)

if interactive {
for _, e := range result {
if !errors.Is(err, display.ErrNotImplemented) {
fmt.Println(e)
}
}
}
return result
}
105 changes: 105 additions & 0 deletions display/text_display.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// Copyright 2024 The Periph Authors. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.

package display

import (
"errors"
)

type CursorDirection int

const (
// Constants for moving the cursor relative to it's current position.
//
// Move the cursor one unit back.
Backward CursorDirection = iota
// Move the cursor one unit forward.
Forward
Up
Down
)

type CursorMode int

const (
// Turn the cursor Off
CursorOff CursorMode = iota
// Enable Underline Cursor
CursorUnderline
// Enable Block Cursor
CursorBlock
// Blinking
CursorBlink
)

// TextDisplay represents an interface to a basic character device. It provides
// standard methods implemented by a majority of character LCD devices. Pixel
// type displays can implement this interface if desired.
type TextDisplay interface {
// Enable/Disable auto scroll
AutoScroll(enabled bool) (err error)
// Return the number of columns the display supports
Cols() int
// Clear the display and move the cursor home.
Clear() (err error)
// Set the cursor mode. You can pass multiple arguments.
// Cursor(CursorOff, CursorUnderline)
//
// Implementations should return an error if the value of mode is not
// mode>= CursorOff && mode <= CursorBlink
Cursor(mode ...CursorMode) (err error)
// Move the cursor home (MinRow(),MinCol())
Home() (err error)
// Return the min column position.
MinCol() int
// Return the min row position.
MinRow() int
// Move the cursor forward or backward.
Move(dir CursorDirection) (err error)
// Move the cursor to arbitrary position. Implementations should return an
// error if row < MinRow() || row > (Rows()-MinRow()), or col < MinCol()
// || col > (Cols()-MinCol())
MoveTo(row, col int) (err error)
// Return the number of rows the display supports.
Rows() int
// Turn the display on / off
Display(on bool) (err error)
// return info about the display.
String() string
// Write a set of bytes to the display.
Write(p []byte) (n int, err error)
// Write a string output to the display.
WriteString(text string) (n int, err error)
}

type Intensity int
Comment thread
maruel marked this conversation as resolved.

// Interface for displays that support a monochrome backlight. Displays that
// support RGB Backlights should implement this as well for maximum
// compatibility.
//
// Many units that support this command write the value to EEPROM, which has a
// finite number of writes. To turn the unit on/off, use TextDisplay.Display()
type DisplayBacklight interface {
Backlight(intensity Intensity) error
}

// Interface for displays that support a RGB Backlight. E.G. the Sparkfun SerLCD
type DisplayRGBBacklight interface {
RGBBacklight(red, green, blue Intensity) error
}

type Contrast int

// Interface for displays that support a programmable contrast adjustment.
// As with SetBacklight(), many devices serialize the value to EEPROM,
// which support only a finite number of writes, so this should be used
// sparingly.
type DisplayContrast interface {
Contrast(contrast Contrast) error
}

var ErrNotImplemented = errors.New("not implemented")
var ErrInvalidCommand = errors.New("invalid command")