Skip to content

Commit

Permalink
display: Create Interfaces for text displays (#42)
Browse files Browse the repository at this point in the history
* Create Interfaces for text displays

* Print errors in interactive mode.
  • Loading branch information
gsexton authored Jan 12, 2025
1 parent 1a89f8f commit e01555d
Show file tree
Hide file tree
Showing 2 changed files with 312 additions and 0 deletions.
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

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

0 comments on commit e01555d

Please sign in to comment.