cfa

package module
v0.0.0-...-65abd42 Latest Latest
Warning

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

Go to latest
Published: Apr 23, 2020 License: BSD-3-Clause Imports: 15 Imported by: 0

README

CFA - interfaces with CrystalFontz USB LCDs

Known to work with CFA631 and CFA635 LCDs; likely to work with others since the command sets have a lot in common. Your kernel will need the appropriate driver(s) - CDC ACM for newer LCDs, usb-serial for older.

boot menu

Known issues

Some tests are occasionally flaky, and the test code is rather ugly. I wasn't sure how to fix the ugliness short of removing tests, which doesn't strike me as the best of options.

Upstream

This is the cfa package that's part of https://github.com/purecloudlabs/gprovision, specifically https://github.com/purecloudlabs/gprovision/tree/master/gopath/src/gprovision/pkg/hw/cfa .

I am duplicating it here to re-write import paths so it's easier to use standalone. Other than the paths, I intend to keep it in sync with upstream - if it diverges, let me know!

Documentation

Overview

Package cfa implements communication with Crystalfontz LCDs such as CFA-631 and CFA-635, without use of CGO. In addition, this package supports keypress events and menus.

Packet Structure, from data sheet

The following can be found beginning on Pg 34, CFA631_Data_Sheet_Release_2014-11-17.pdf.

All packets have the following structure:

<type><data_length><data><CRC>

type is one byte, and identifies the type and function of the packet:

TTcc cccc
|||| ||||--Command, response, error or report code 0-63
||---------Type:
           00 = normal command from host to CFA631
           01 = normal response from CFA631 to host
           10 = normal report from CFA631 to host (not in direct response to a command from the host)
           11 = error response from CFA631 to host (a packet with valid structure but illegal content was received by the CFA631)

data_length specifies the number of bytes that will follow in the data field. The valid range of data_length is 0 to 22.

data is the payload of the packet. Each type of packet will have a specified data_length and format for data as well as algorithms for decoding data detailed below.

CRC is a standard 16-bit CRC of all the bytes in the packet except the CRC itself. The CRC is sent LSB first. At the port, the CRC immediately follows the last used element of data []. See Sample Algorithms To Calculate The CRC (Pg. 66) for details.

The following C definition may be useful for understanding the packet structure.

typedef struct
{
   unsigned char command;
   unsigned char data_length;
   unsigned char data[MAX_DATA_LENGTH];
   unsigned short CRC;
} COMMAND_PACKET;

Packet Notes

While the documentation can be interpreted as saying the packet size is fixed, it is not; there is never padding between the last valid data byte and the crc, and the packet length is always data_length+4.

Crystalfontz claims above that the CRC used is standard, but a bit of googling leads me to the conclusion that there is no such thing. There are myriad variations of 16-bit CRC with different constants, and the sites discussing it tend to disagree on which constants are used by which protocols. Very few standards actually include any test vectors. Crystalfontz' own data sheets for the 631 and 635 include a test vector in one example... but the output listed does not match the value computed by the "crystalfontz linux example", which is able to talk to the LCD.

Known Issues

The CFA-631 and XES-635BK-TML-KU work fine, but the XES-635BK-TMF-KU can hang or otherwise lose packets. By far the most effective workaround seems to be to minimize the number of packets sent. Of course, that's not possible beyond a certain point without throwing usability out the window.

We have shipped some XES-635BK-TFE-KU. The only difference from the TMF should be the display/backlight color, so those are likely to have the same issues as TMF.

Buttons and events

Key event reports (and responses to the key poll command) are written to an event channel for async read.

Only key releases are considered. Reading key press events or keys being held down (only seen when polling) would not be difficult, but handling them with the menu would complicate things.

Index

Constants

View Source
const (
	SymLastCGRAM byte = iota + 0x0f

	SymRight      //triangular right arrow
	SymLeft       //triangular left arrow
	SymDoubleUp   //double up arrow
	SymDoubleDown //double down arrow
	SymLtLt       //double less-than (quotation mark in some languages)
	SymGtGt       //double greater-than (quotation mark in some langs)
	SymULArr      //diagonal arrow upper left
	SymURArr      //diagonal arrow upper right
	SymLLArr      //diagonal arrow lower left
	SymLRArr      //diagonal arrow lower right
	SymUp         //triangular up arrow
	SymDown       //triangular down arrow
	SymEnter      //enter key arrow symbol
	SymCaret      //caret, aka circumflex
	SymCaron      //upside-down circumflex
	SymFilled     //all pixels on
	SymSpace      //all pixels off
)

symbols for display on LCD

View Source
const (
	LowestErrVal    Command = 0xC0
	LowestReportVal         = 0x80
	LowestOKVal             = 0x40
)
View Source
const (
	KeyPollCurrent int = iota
	KeyPollPressed
	KeyPollReleased
)

indexes into data returned for Cmd_ReadKeysPolled

View Source
const (
	MaxTries           = 5                      //number of re-transmissions before giving up
	PktResponseWaitMax = 300 * time.Millisecond //pdf says 250mS + OS overhead
)

consts related to packet transmission

View Source
const (
	DbgPktRW dbgFlags = 1 << iota
	DbgPktErr
	DbgGeneral
	DbgMenu
	DbgTraceSer
	DbgTraceSerEnter
)
View Source
const MAX_DATA_LENGTH = 22

Variables

View Source
var EFit = fmt.Errorf("Will not fit on display")
View Source
var ELen = fmt.Errorf("String length out of range")
View Source
var EMissing = fmt.Errorf("No LCD found")
View Source
var ENil = fmt.Errorf("Lcd is nil")
View Source
var EPacket = fmt.Errorf("Bad packet")
View Source
var ERange = fmt.Errorf("Coordinate(s) out of range")
View Source
var ERetry = fmt.Errorf("Reached max number of retries")
View Source
var MockVerbose bool = true
View Source
var TraceEnter bool

Functions

func CfCrc

func CfCrc(bytes []byte) (crc uint16)

func DoneChToTimeCh

func DoneChToTimeCh(done chan struct{}) <-chan time.Time

Convert done (chan struct{}) to a Time channel. For use with l.waitForEvent.

func FindDevs

func FindDevs() (devs []string)

look for usb serial devices that use the cdc or ftdi driver and appear to be Crystalfontz LCDs.

func FlagsFromString

func FlagsFromString(bits string) dbgFlags

func NewBlurb

func NewBlurb(l *Lcd, txt LcdTxt, start, dims Coord) blurb

Chooses scrolling type best suited for text size and allowed dims, returns blurb implementing this. Caution: legend width must not change once blurb is created, or blurb is likely to fail to draw.

func NewHorizScroller

func NewHorizScroller(l *Lcd, txt LcdTxt, start, dims Coord) blurb

func NewMockKeygen

func NewMockKeygen(l *Lcd, tlog Tlog, seq mockKeySequence, tickCount, headroom int) *mockKeygen

func NewNoScroller

func NewNoScroller(l *Lcd, txt []LcdTxt, start, dims Coord) blurb

func NewVertScroller

func NewVertScroller(l *Lcd, lines []LcdTxt, start, dims Coord) blurb

func Uninit

func Uninit(_ bool)

Called as factory restore and mfg exit

Types

type Answer

type Answer int
const (
	ANSWER_NA Answer = iota //no choice made - timeout
	ANSWER_NO
	ANSWER_YES
)

type Choice

type Choice int

User selection from menu. Non-negative values correspond to menu item indexes.

const (
	CHOICE_NONE   Choice = -1 - iota
	CHOICE_CANCEL        //-2
)

type Command

type Command byte
A command in a transmitted packet or a response code in a received packet.

When uncommenting additional commands, remember to add them to the String() method.

const (
	//Commands sent to LCD
	Cmd_Ping     Command = iota //0x00
	Cmd_HwFwVers                //0x01

	Cmd_ReadUserFlash //0x03

	Cmd_Reboot //0x05
	Cmd_Clear  //0x06

	Cmd_SetCursorPos   //0x0b
	Cmd_SetCursorStyle //0x0c

	Cmd_SetBacklight //0x0e

	Cmd_CfgKeyReports  //0x17
	Cmd_ReadKeysPolled //0x18

	Cmd_ReadReprtStat      //0x1e
	Cmd_WriteDisp          //0x1f
	Cmd_KeyLegendOnOffMask //0x20

	//Reports from the LCD
	Report_Key = 0x80
)
const CmdMask Command = 0x3f

Masks the packet type bytes

func (Command) CommandFromResponse

func (c Command) CommandFromResponse() Command

func (Command) String

func (c Command) String() string

Returns a string representing the command, with no whitespace. Lack of whitespace seems to make logs slightly easier to read. A 4-character prefix indicates the class of command (actual command, ok response, report, error).

func (Command) Type

func (c Command) Type() PktType

type Coord

type Coord struct{ Col, Row byte }

type FadeStyle

type FadeStyle int
const (
	Flash FadeStyle = iota //abrupt - square wave
	Fade                   //gradual - sine wave
)

type KeyActivity

type KeyActivity byte
const (
	KEY_NO_KEY KeyActivity = iota
	/* CFA-635 (external: XES635) */
	KEY_UP_PRESS //1
	KEY_DOWN_PRESS
	KEY_LEFT_PRESS
	KEY_RIGHT_PRESS
	KEY_ENTER_PRESS
	KEY_EXIT_PRESS
	KEY_UP_RELEASE
	KEY_DOWN_RELEASE
	KEY_LEFT_RELEASE
	KEY_RIGHT_RELEASE
	KEY_ENTER_RELEASE
	KEY_EXIT_RELEASE //12
	/* CFA-631 */
	KEY_UL_PRESS //13
	KEY_UR_PRESS
	KEY_LL_PRESS
	KEY_LR_PRESS
	KEY_UL_RELEASE
	KEY_UR_RELEASE
	KEY_LL_RELEASE
	KEY_LR_RELEASE //20
)

type KeyMask

type KeyMask byte
const (
	KP_UL KeyMask = 1 << iota
	KP_UR
	KP_LL
	KP_LR
	KP_ALL_631 = KP_UL | KP_UR | KP_LL | KP_LR
)

CFA 631: 4 buttons

const (
	KP_UP    KeyMask = 1 << iota
	KP_ENTER         //check mark
	KP_EXIT          //CF docs call this KP_CANCEL. renamed for consistency.
	KP_LEFT
	KP_RIGHT
	KP_DOWN
	KP_UVDX_635 = KP_UP | KP_DOWN | KP_ENTER | KP_EXIT
	KP_LVRX_635 = KP_ENTER | KP_EXIT | KP_LEFT | KP_RIGHT
	KP_ALL_635  = KP_UVDX_635 | KP_LEFT | KP_RIGHT
)

CFA 635: 6 buttons

type KeyXlate

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

for keymaskToActivity

type Lcd

type Lcd struct {
	DbgGeneral bool //log info to aid in debugging
	DbgMenu    bool //debug menus
	// contains filtered or unexported fields
}
var DefaultLcd *Lcd

packages can use this rather than calling Find() and maintaining a local *Lcd.

func ConnectTo

func ConnectTo(dev string) (*Lcd, error)

Connect to an lcd given its port name, such as '/dev/ttyACM0'. Stores mutable state so it can be written back later.

func Find

func Find() (*Lcd, error)

Preferred method of getting an Lcd{}. Finds lcd serial port, connects to it. If one has already been found, return that. Stores mutable state so it can be written back later.

func FindWithRetry

func FindWithRetry() (*Lcd, error)

like Find() but tries multiple times.

func (*Lcd) BlinkMsg

func (l *Lcd) BlinkMsg(msg string, fade FadeStyle, cycle, displayTime time.Duration) (err error)

Like LongMsg, but changes brightness to get attention

func (*Lcd) BlinkMsgUntil

func (l *Lcd) BlinkMsgUntil(done chan struct{}, msg string, fade FadeStyle, cycle time.Duration) (err error)

Like BlinkMsg but call from goroutine; displays until `done` is closed.

func (*Lcd) Clear

func (l *Lcd) Clear() error

Clear screen and put cursor at 0,0

func (*Lcd) Close

func (l *Lcd) Close() error

Restore mutable state to lcd and close port.

func (*Lcd) ConfirmChoice

func (l *Lcd) ConfirmChoice(desc string, items []LcdTxt, choice Choice, timeout time.Duration) Answer

From %s, you chose %s. Are you certain?

>No< Yes

func (*Lcd) Debug

func (l *Lcd) Debug(flags dbgFlags)

func (*Lcd) DefaultBacklight

func (l *Lcd) DefaultBacklight() error

func (*Lcd) Event

func (l *Lcd) Event() (ka KeyActivity)

Return an event if one has occurred. Short delay.

func (*Lcd) Fits

func (l *Lcd) Fits(txt LcdTxt) bool

true if txt will fit on screen

func (*Lcd) FlushEvents

func (l *Lcd) FlushEvents()

Clear any outstanding events

func (*Lcd) HideCursor

func (l *Lcd) HideCursor()

func (*Lcd) LongMsg

func (l *Lcd) LongMsg(msg string, cycle, displayTime time.Duration) error

Scroll a long message vertically. Non-ASCII chars will not render correctly.

func (*Lcd) LongMsgUntil

func (l *Lcd) LongMsgUntil(done chan struct{}, msg string, cycle time.Duration) (err error)

func (*Lcd) MaxCursorPos

func (l *Lcd) MaxCursorPos() Coord

return max cursor position (add 1 to X and Y for screen dims)

func (*Lcd) Menu

func (l *Lcd) Menu(items []LcdTxt, timeout time.Duration, keyPolling bool) Choice

Menu creates a menu with the given items, one per line, navigable using the LCD buttons. Vertical scrolling is allowed when the number of items exceeds the LCD's height. When an item's length exceeds display width, that item automatically scrolls horizontally - with a pause at beginning and end.

If non-negative, the return value indicates the index of the item selected by the user. Two negative values are also possible: CHOICE_NONE (-1) if no choice is made within the timeout, or CHOICE_CANCEL (-2) if the user presses the cancel button.

On the CFA-631, the legend is enabled to indicate to the user what the buttons do. The width of this legend is taken into account for rendering and scrolling.

TODO: add symbol to cursor?

func (*Lcd) MenuWithConfirm

func (l *Lcd) MenuWithConfirm(desc string, items []LcdTxt, menuTime, confirmTime time.Duration, keyPolling bool) (Choice, Answer)

Like menu, but confirmation required. Re-displays menu if choice is not confirmed.

func (*Lcd) Model

func (l *Lcd) Model() Model

returns detected device model

func (*Lcd) Msg

func (l *Lcd) Msg(msg string) (Coord, error)

Msg writes an ASCII message. It returns row,col of last character - same as used with Update() function. Do not use with non-ASCII chars, as they are likely to be converted to utf-8 and corrupted in the process.

func (*Lcd) NewQuestion

func (l *Lcd) NewQuestion(txt LcdTxt, opts []LcdTxt) (*Question, error)

func (*Lcd) Ping

func (l *Lcd) Ping() (bool, error)

func (*Lcd) PollKeys

func (l *Lcd) PollKeys() error

Poll lcd for key activity. After a short delay, results will be available to Event() or WaitForEvent().

func (*Lcd) PressAnyKey

func (l *Lcd) PressAnyKey(desc string, cycle, timeout time.Duration) (pressed bool, err error)

display message with timeout, return true if a button was pressed during that time TODO display remaining time on screen?

func (*Lcd) PressAnyKeyUntil

func (l *Lcd) PressAnyKeyUntil(desc string, cycle time.Duration, done chan struct{}) (pressed bool, err error)

Same as PressAnyKey, but wait for a channel instead of using a timeout.

func (*Lcd) Revision

func (l *Lcd) Revision() (info string, err error)

returns raw model/revision string from device

func (*Lcd) SetBacklight

func (l *Lcd) SetBacklight(bright uint8) error

func (*Lcd) SetCursorPosition

func (l *Lcd) SetCursorPosition(p Coord)

func (*Lcd) SetCursorStyle

func (l *Lcd) SetCursorStyle(s byte)

func (*Lcd) SetupKeyReporting

func (l *Lcd) SetupKeyReporting(press, release bool)

configure reporting of key press and release events

func (*Lcd) SpinnerUntil

func (l *Lcd) SpinnerUntil(msg string, interval time.Duration, done chan struct{}) (err error)

Displays spinner until 'done' is closed. Interval must be at least 1s.

func (*Lcd) Uninit

func (l *Lcd) Uninit(_ bool)

func (*Lcd) WaitForEvent

func (l *Lcd) WaitForEvent(maxWait time.Duration) (ka KeyActivity)

Return an event, or return nothing (KEY_NO_KEY) if maxWait exceeded.

func (*Lcd) Width

func (l *Lcd) Width() byte

return number of usable chars on a line, taking into account overlay

func (*Lcd) Write

func (l *Lcd) Write(start Coord, txt LcdTxt) (err error)

Write text to the screen beginning at 'start'

func (*Lcd) YesNo

func (l *Lcd) YesNo(txt LcdTxt, timeout time.Duration) Answer

type LcdTxt

type LcdTxt []byte

Characters to be displayed on the screen. Generally used for text that fits on a single line. Must use byte arrays rather than strings, as non-ascii characters will otherwise get translated into utf-8 before the lcd sees them, resulting in gibberish.

func Strs2LTxt

func Strs2LTxt(strs ...string) []LcdTxt

Convert string arg(s) into array of LcdTxt. More compact/less painful than wrapping every individual string in LcdTxt()

type Legend

type Legend int

Type of legend (only has an effect on cfa631)

const (
	//V = check mark, X = cancel/exit
	LegendNone Legend = iota //legend is disabled
	LegendUVDX               //top:    up, check; bottom:  down, X
	Legend_VDX               //top: blank, check; bottom:  down, X
	LegendUV_X               //top:    up, check; bottom: blank, X
	LegendLVRX               //top:  left, check; bottom: right, X
	Legend_VRX               //top: blank, check; bottom: right, X
	LegendLV_X               //top:  left, check; bottom: blank, X
)

func (Legend) Values

func (l Legend) Values() (lv LegendValues)

type LegendSymbol

type LegendSymbol byte

Codes for legend symbols for overlay

const (
	LegendSymBlank LegendSymbol = iota
	LegendSymExit               //AKA Cancel or 'X'
	LegendSymCheck
	LegendSymUp
	LegendSymDown
	LegendSymRight
	LegendSymLeft
	LegendSymPlus
	LegendSymMinus
	LegendSymNone
)

type LegendValues

type LegendValues struct {
	Enable         bool
	UL, UR, LL, LR LegendSymbol
}

Only for CFA631

func (LegendValues) Bytes

func (lv LegendValues) Bytes() []byte

type Model

type Model int
const (
	Cfa631 Model = iota
	Cfa635
)

type Packet

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

incoming packets do include the crc

func GetPacket

func GetPacket(r ReadFlusher, DbgPktErr, DbgRW bool) (p *Packet, err error)

Stuffs incoming data from the serial port into packets. Checks packet type, verifies CRC.

func (*Packet) Cmd

func (p *Packet) Cmd() Command

note that the returned value includes the `type` bits

func (*Packet) Crc

func (p *Packet) Crc() (crc uint16, buf *bytes.Buffer, err error)

Calculate Crystalfontz CRC for packet

func (*Packet) Data

func (p *Packet) Data() []byte

func (*Packet) Log

func (p *Packet) Log(crc uint16, read bool)

Logs packet (+CRC) and whether it was read or written.

func (*Packet) SetCommand

func (p *Packet) SetCommand(cmd Command) error

func (*Packet) SetData

func (p *Packet) SetData(data []byte) error

func (*Packet) String

func (p *Packet) String() string

String method so that Printf (etc) can easily render the packet

func (*Packet) Type

func (p *Packet) Type() PktType

func (*Packet) Validate

func (p *Packet) Validate() bool

check crc

func (*Packet) WriteTo

func (p *Packet) WriteTo(w io.Writer, verbose bool) error

calculate crc, write packet out

type PktType

type PktType byte
const (
	CFCommand  PktType = iota //0 (00)
	CFResponse                //1 (01)
	CFReport                  //2 (10)
	CFError                   //3 (11)
)

type Question

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

func (*Question) Ask

func (q *Question) Ask(timeout time.Duration) Choice

type ReadFlusher

type ReadFlusher interface {
	Read([]byte) (int, error)
	Flush() error
}

type passed to Read() and badPacket(); subset of SerialPort

type SerialDev

type SerialDev struct {
	Events chan KeyActivity //incoming button/key events
	In     chan *Packet     //incoming packets except above events

	// xlateTable is used to translate polled key data to events. Init is a bit
	// chicken-and-egg since we need the model to init this, and the serial dev
	// must be up to discover the model.
	XlateTable []KeyXlate

	DbgRW          bool          //if true, log content of every packet read or written
	DbgPktErr      bool          //if true, log packet errors even if they will be retried
	MinPktInterval time.Duration //dead time between packets
	// contains filtered or unexported fields
}

SerialDev translates Crystalfontz-compatible packets into serial byte streams and vice versa. It intercepts and decodes incoming key reporting and polling packets, populating Events. Other packets go into In.

func Mock

func Mock(m Model, wg *sync.WaitGroup, useHex bool) (sd *SerialDev)

func SerialSetup

func SerialSetup(dev string) (sd *SerialDev, err error)

set up port, init channels, start bg process

func (*SerialDev) Close

func (sd *SerialDev) Close() error

type SerialPort

type SerialPort interface {
	Close() error
	Read([]byte) (int, error)
	Write([]byte) (int, error)
	Flush() error
}

type Spinner

type Spinner struct {
	Msg string

	Lcd *Lcd
	// contains filtered or unexported fields
}

Spinner is a message + ASCII progress spinner. Start with Display(), update with spinner.Next().

NOTE - will not prevent, detect, or recover gracefully from something else changing the display.

func (*Spinner) Display

func (s *Spinner) Display() error

clear display and show s.Msg also calculate coords for spinner put a space between message and spinner when possible

func (*Spinner) Next

func (s *Spinner) Next()

Advance spinner to next state. Rate limited to < 1/s

type TickDistrib

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

A TickDistrib copies each tick on 'in' to all output channels. Used to signal to multiple ui elements that they can update.

func NewTestTickDistrib

func NewTestTickDistrib(in <-chan time.Time, n int, relent time.Duration, depth int, dbg bool) *TickDistrib

like NewTickDistrib, but for testing. exposes extra knobs

func NewTickDistrib

func NewTickDistrib(in <-chan time.Time, n int) *TickDistrib

create a TickDistrib with n output channels

func (*TickDistrib) Get

func (td *TickDistrib) Get(i uint) <-chan time.Time

func (*TickDistrib) Stop

func (td *TickDistrib) Stop()

type Ticker

type Ticker struct {
	C <-chan time.Time

	In chan time.Time
	// contains filtered or unexported fields
}

can encompass a time.Ticker, or be a mock suitable for testing.

func NewMockTicker

func NewMockTicker(maxCount int, minInterval time.Duration) *Ticker

NewMockTicker creates a ticker that will provide the given number of ticks and minimum interval between them.

func NewTicker

func NewTicker(p time.Duration) *Ticker

NewTicker creates a Ticker with behavior identical to time.Ticker.

func NewTickerFromChan

func NewTickerFromChan(c <-chan time.Time) *Ticker

NewTickerFromChan creates a Ticker whose C is the given channel. Use case: testing functions that take a ticker, but you have a TickDistrib instead.

func (*Ticker) Stop

func (t *Ticker) Stop()

type Tlog

type Tlog interface {
	TstErrf(f string, va ...interface{})
	TstLogf(f string, va ...interface{})
	Freeze()
	Logf(f string, va ...interface{})
}

satisfied by gprovision/pkg/log/testlog

Directories

Path Synopsis
Demo app to exercise a compatible Crystalfontz LCD.
Demo app to exercise a compatible Crystalfontz LCD.
Package serial configures a serial port for use with Crystalfontz LCDs.
Package serial configures a serial port for use with Crystalfontz LCDs.

Jump to

Keyboard shortcuts

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