Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

display: Create Interfaces for text displays #42

Merged
merged 10 commits into from
Jan 12, 2025
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
}

Check warning on line 26 in display/displaytest/text.go

View check run for this annotation

Codecov / codecov/patch

display/displaytest/text.go#L19-L26

Added lines #L19 - L26 were not covered by tests
// Turn the dev on and write the String() value.
if err = dev.Display(true); err != nil {
result = append(result, err)
}

Check warning on line 30 in display/displaytest/text.go

View check run for this annotation

Codecov / codecov/patch

display/displaytest/text.go#L28-L30

Added lines #L28 - L30 were not covered by tests

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

Check warning on line 34 in display/displaytest/text.go

View check run for this annotation

Codecov / codecov/patch

display/displaytest/text.go#L32-L34

Added lines #L32 - L34 were not covered by tests

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)
}

Check warning on line 52 in display/displaytest/text.go

View check run for this annotation

Codecov / codecov/patch

display/displaytest/text.go#L36-L52

Added lines #L36 - L52 were not covered by tests
// 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

Check warning on line 68 in display/displaytest/text.go

View check run for this annotation

Codecov / codecov/patch

display/displaytest/text.go#L54-L68

Added lines #L54 - L68 were not covered by tests
}
}
// 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)
}

Check warning on line 103 in display/displaytest/text.go

View check run for this annotation

Codecov / codecov/patch

display/displaytest/text.go#L72-L103

Added lines #L72 - L103 were not covered by tests
}
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))
}

Check warning on line 120 in display/displaytest/text.go

View check run for this annotation

Codecov / codecov/patch

display/displaytest/text.go#L105-L120

Added lines #L105 - L120 were not covered by tests
}

// 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)
}

Check warning on line 144 in display/displaytest/text.go

View check run for this annotation

Codecov / codecov/patch

display/displaytest/text.go#L124-L144

Added lines #L124 - L144 were not covered by tests
}
if err = dev.Cursor(display.CursorBlink + 1); err == nil {
result = append(result, errors.New("did not receive expected error on Cursor() with invalid value"))
}

Check warning on line 148 in display/displaytest/text.go

View check run for this annotation

Codecov / codecov/patch

display/displaytest/text.go#L146-L148

Added lines #L146 - L148 were not covered by tests

// 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)
}

Check warning on line 170 in display/displaytest/text.go

View check run for this annotation

Codecov / codecov/patch

display/displaytest/text.go#L151-L170

Added lines #L151 - L170 were not covered by tests
}
if err = dev.Move(display.Down + 1); err == nil {
result = append(result, errors.New("did not receive expected error on Move() with invalid value"))
}

Check warning on line 174 in display/displaytest/text.go

View check run for this annotation

Codecov / codecov/patch

display/displaytest/text.go#L172-L174

Added lines #L172 - L174 were not covered by tests

// 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)
}

Check warning on line 203 in display/displaytest/text.go

View check run for this annotation

Codecov / codecov/patch

display/displaytest/text.go#L177-L203

Added lines #L177 - L203 were not covered by tests
}
}
return result

Check warning on line 206 in display/displaytest/text.go

View check run for this annotation

Codecov / codecov/patch

display/displaytest/text.go#L206

Added line #L206 was not covered by tests
}
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
maruel marked this conversation as resolved.
Show resolved Hide 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")
Loading