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
- Variables
- func CfCrc(bytes []byte) (crc uint16)
- func DoneChToTimeCh(done chan struct{}) <-chan time.Time
- func FindDevs() (devs []string)
- func FlagsFromString(bits string) dbgFlags
- func NewBlurb(l *Lcd, txt LcdTxt, start, dims Coord) blurb
- func NewHorizScroller(l *Lcd, txt LcdTxt, start, dims Coord) blurb
- func NewMockKeygen(l *Lcd, tlog Tlog, seq mockKeySequence, tickCount, headroom int) *mockKeygen
- func NewNoScroller(l *Lcd, txt []LcdTxt, start, dims Coord) blurb
- func NewVertScroller(l *Lcd, lines []LcdTxt, start, dims Coord) blurb
- func Uninit(_ bool)
- type Answer
- type Choice
- type Command
- type Coord
- type FadeStyle
- type KeyActivity
- type KeyMask
- type KeyXlate
- type Lcd
- func (l *Lcd) BlinkMsg(msg string, fade FadeStyle, cycle, displayTime time.Duration) (err error)
- func (l *Lcd) BlinkMsgUntil(done chan struct{}, msg string, fade FadeStyle, cycle time.Duration) (err error)
- func (l *Lcd) Clear() error
- func (l *Lcd) Close() error
- func (l *Lcd) ConfirmChoice(desc string, items []LcdTxt, choice Choice, timeout time.Duration) Answer
- func (l *Lcd) Debug(flags dbgFlags)
- func (l *Lcd) DefaultBacklight() error
- func (l *Lcd) Event() (ka KeyActivity)
- func (l *Lcd) Fits(txt LcdTxt) bool
- func (l *Lcd) FlushEvents()
- func (l *Lcd) HideCursor()
- func (l *Lcd) LongMsg(msg string, cycle, displayTime time.Duration) error
- func (l *Lcd) LongMsgUntil(done chan struct{}, msg string, cycle time.Duration) (err error)
- func (l *Lcd) MaxCursorPos() Coord
- func (l *Lcd) Menu(items []LcdTxt, timeout time.Duration, keyPolling bool) Choice
- func (l *Lcd) MenuWithConfirm(desc string, items []LcdTxt, menuTime, confirmTime time.Duration, ...) (Choice, Answer)
- func (l *Lcd) Model() Model
- func (l *Lcd) Msg(msg string) (Coord, error)
- func (l *Lcd) NewQuestion(txt LcdTxt, opts []LcdTxt) (*Question, error)
- func (l *Lcd) Ping() (bool, error)
- func (l *Lcd) PollKeys() error
- func (l *Lcd) PressAnyKey(desc string, cycle, timeout time.Duration) (pressed bool, err error)
- func (l *Lcd) PressAnyKeyUntil(desc string, cycle time.Duration, done chan struct{}) (pressed bool, err error)
- func (l *Lcd) Revision() (info string, err error)
- func (l *Lcd) SetBacklight(bright uint8) error
- func (l *Lcd) SetCursorPosition(p Coord)
- func (l *Lcd) SetCursorStyle(s byte)
- func (l *Lcd) SetupKeyReporting(press, release bool)
- func (l *Lcd) SpinnerUntil(msg string, interval time.Duration, done chan struct{}) (err error)
- func (l *Lcd) Uninit(_ bool)
- func (l *Lcd) WaitForEvent(maxWait time.Duration) (ka KeyActivity)
- func (l *Lcd) Width() byte
- func (l *Lcd) Write(start Coord, txt LcdTxt) (err error)
- func (l *Lcd) YesNo(txt LcdTxt, timeout time.Duration) Answer
- type LcdTxt
- type Legend
- type LegendSymbol
- type LegendValues
- type Model
- type Packet
- func (p *Packet) Cmd() Command
- func (p *Packet) Crc() (crc uint16, buf *bytes.Buffer, err error)
- func (p *Packet) Data() []byte
- func (p *Packet) Log(crc uint16, read bool)
- func (p *Packet) SetCommand(cmd Command) error
- func (p *Packet) SetData(data []byte) error
- func (p *Packet) String() string
- func (p *Packet) Type() PktType
- func (p *Packet) Validate() bool
- func (p *Packet) WriteTo(w io.Writer, verbose bool) error
- type PktType
- type Question
- type ReadFlusher
- type SerialDev
- type SerialPort
- type Spinner
- type TickDistrib
- type Ticker
- type Tlog
Constants ¶
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
const ( LowestErrVal Command = 0xC0 LowestReportVal = 0x80 LowestOKVal = 0x40 )
const ( KeyPollCurrent int = iota KeyPollPressed KeyPollReleased )
indexes into data returned for Cmd_ReadKeysPolled
const ( MaxTries = 5 //number of re-transmissions before giving up PktResponseWaitMax = 300 * time.Millisecond //pdf says 250mS + OS overhead )
consts related to packet transmission
const ( DbgPktRW dbgFlags = 1 << iota DbgPktErr DbgGeneral DbgMenu DbgTraceSer DbgTraceSerEnter )
const MAX_DATA_LENGTH = 22
Variables ¶
var EFit = fmt.Errorf("Will not fit on display")
var ELen = fmt.Errorf("String length out of range")
var EMissing = fmt.Errorf("No LCD found")
var ENil = fmt.Errorf("Lcd is nil")
var EPacket = fmt.Errorf("Bad packet")
var ERange = fmt.Errorf("Coordinate(s) out of range")
var ERetry = fmt.Errorf("Reached max number of retries")
var MockVerbose bool = true
var TraceEnter bool
Functions ¶
func DoneChToTimeCh ¶
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 ¶
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 NewMockKeygen ¶
func NewNoScroller ¶
func NewVertScroller ¶
Types ¶
type Choice ¶
type Choice int
User selection from menu. Non-negative values correspond to menu item indexes.
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 ¶
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
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 ¶
Connect to an lcd given its port name, such as '/dev/ttyACM0'. Stores mutable state so it can be written back later.
func Find ¶
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 (*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) 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) DefaultBacklight ¶
func (*Lcd) Event ¶
func (l *Lcd) Event() (ka KeyActivity)
Return an event if one has occurred. Short delay.
func (*Lcd) HideCursor ¶
func (l *Lcd) HideCursor()
func (*Lcd) LongMsgUntil ¶
func (*Lcd) MaxCursorPos ¶
return max cursor position (add 1 to X and Y for screen dims)
func (*Lcd) Menu ¶
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) Msg ¶
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) PollKeys ¶
Poll lcd for key activity. After a short delay, results will be available to Event() or WaitForEvent().
func (*Lcd) PressAnyKey ¶
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) SetBacklight ¶
func (*Lcd) SetCursorPosition ¶
func (*Lcd) SetCursorStyle ¶
func (*Lcd) SetupKeyReporting ¶
configure reporting of key press and release events
func (*Lcd) SpinnerUntil ¶
Displays spinner until 'done' is closed. Interval must be at least 1s.
func (*Lcd) WaitForEvent ¶
func (l *Lcd) WaitForEvent(maxWait time.Duration) (ka KeyActivity)
Return an event, or return nothing (KEY_NO_KEY) if maxWait exceeded.
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.
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 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) SetCommand ¶
type ReadFlusher ¶
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 SerialSetup ¶
set up port, init channels, start bg process
type SerialPort ¶
type Spinner ¶
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.
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) 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 ¶
NewMockTicker creates a ticker that will provide the given number of ticks and minimum interval between them.
func NewTickerFromChan ¶
NewTickerFromChan creates a Ticker whose C is the given channel. Use case: testing functions that take a ticker, but you have a TickDistrib instead.