diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e926963..67528f6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -128,6 +128,7 @@ jobs: go install github.com/securego/gosec/v2/cmd/gosec@latest go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow@latest go install honnef.co/go/tools/cmd/staticcheck@latest + go install golang.org/x/tools/cmd/stringer@latest - name: 'go install necessary tools (ubuntu)' if: always() && matrix.os == 'ubuntu-latest' run: | diff --git a/inky/example_test.go b/inky/example_test.go index e3e4583..c9082c7 100644 --- a/inky/example_test.go +++ b/inky/example_test.go @@ -12,6 +12,7 @@ import ( "os" "periph.io/x/conn/v3/gpio/gpioreg" + "periph.io/x/conn/v3/i2c/i2creg" "periph.io/x/conn/v3/spi/spireg" "periph.io/x/devices/v3/inky" "periph.io/x/host/v3" @@ -58,3 +59,52 @@ func Example() { log.Fatal(err) } } + +func ExampleNewImpression() { + path := flag.String("image", "", "Path to image file (600x448) to display") + flag.Parse() + + f, err := os.Open(*path) + if err != nil { + log.Fatal(err) + } + defer f.Close() + + m, _, err := image.Decode(f) + if err != nil { + log.Fatal(err) + } + + if _, err = host.Init(); err != nil { + log.Fatal(err) + } + + b, err := spireg.Open("SPI0.0") + if err != nil { + log.Fatal(err) + } + + dc := gpioreg.ByName("22") + reset := gpioreg.ByName("27") + busy := gpioreg.ByName("17") + + eeprom, err := i2creg.Open("") + if err != nil { + log.Fatal(err) + } + defer eeprom.Close() + + o, err := inky.DetectOpts(eeprom) + if err != nil { + log.Fatal(err) + } + + dev, err := inky.NewImpression(b, dc, reset, busy, o) + if err != nil { + log.Fatal(err) + } + + if err := dev.Draw(m.Bounds(), m, image.Point{}); err != nil { + log.Fatal(err) + } +} diff --git a/inky/impression.go b/inky/impression.go new file mode 100644 index 0000000..a24f918 --- /dev/null +++ b/inky/impression.go @@ -0,0 +1,414 @@ +// Copyright 2023 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 inky + +import ( + "encoding/binary" + "fmt" + "image" + "image/color" + "image/draw" + "log" + "time" + + "periph.io/x/conn/v3" + "periph.io/x/conn/v3/display" + "periph.io/x/conn/v3/gpio" + "periph.io/x/conn/v3/physic" + "periph.io/x/conn/v3/spi" +) + +var _ display.Drawer = &DevImpression{} +var _ conn.Resource = &DevImpression{} +var _ draw.Image = &DevImpression{} + +var ( + // For more: https://github.com/pimoroni/inky/issues/115#issuecomment-887453065 + dsc = []color.NRGBA{ + {0, 0, 0, 0}, // Black + {255, 255, 255, 255}, // White + {0, 255, 0, 255}, // Green + {0, 0, 255, 255}, // Blue + {255, 0, 0, 255}, // Red + {255, 255, 0, 255}, // Yellow + {255, 140, 0, 255}, // Orange + {255, 255, 255, 255}, + } + + sc = []color.NRGBA{ + {57, 48, 57, 0}, // Black + {255, 255, 255, 255}, // White + {58, 91, 70, 255}, // Green + {61, 59, 94, 255}, // Blue + {156, 72, 75, 255}, // Red + {208, 190, 71, 255}, // Yellow + {177, 106, 73, 255}, // Orange + {255, 255, 255, 255}, + } +) + +const ( + uc8159PSR = 0x00 + uc8159PWR = 0x01 + uc8159POF = 0x02 + uc8159PFS = 0x03 + uc8159PON = 0x04 + uc8159BTST = 0x06 + uc8159DSLP = 0x07 + uc8159DTM1 = 0x10 + uc8159DSP = 0x11 + uc8159DRF = 0x12 + uc8159IPC = 0x13 + uc8159PLL = 0x30 + uc8159TSC = 0x40 + uc8159TSE = 0x41 + uc8159TSW = 0x42 + uc8159TSR = 0x43 + uc8159CDI = 0x50 + uc8159LPD = 0x51 + uc8159TCON = 0x60 + uc8159TRES = 0x61 + uc8159DAM = 0x65 + uc8159REV = 0x70 + uc8159FLG = 0x71 + uc8159AMV = 0x80 + uc8159VV = 0x81 + uc8159VDCS = 0x82 + uc8159PWS = 0xE3 + uc8159TSSET = 0xE5 +) + +// DevImpression is a handle to an Inky Impression. +type DevImpression struct { + *Dev + + // Color Palette used to convert images to the 7 color. + Palette color.Palette + // Representation of the pixels. + Pix []uint8 + + // Saturation level used by the color palette. + saturation uint + // Resolution magic number used for resetting the panel. + res int +} + +// NewImpression opens a handle to an Inky Impression. +func NewImpression(p spi.Port, dc gpio.PinOut, reset gpio.PinOut, busy gpio.PinIn, o *Opts) (*DevImpression, error) { + if o.ModelColor != Multi { + return nil, fmt.Errorf("unsupported color: %v", o.ModelColor) + } + + c, err := p.Connect(3000*physic.KiloHertz, spi.Mode0, cs0Pin) + if err != nil { + return nil, fmt.Errorf("failed to connect to inky over spi: %v", err) + } + + // Get the maxTxSize from the conn if it implements the conn.Limits interface, + // otherwise use 4096 bytes. + maxTxSize := 0 + if limits, ok := c.(conn.Limits); ok { + maxTxSize = limits.MaxTxSize() + } + if maxTxSize == 0 { + maxTxSize = 4096 // Use a conservative default. + } + + d := &DevImpression{ + Dev: &Dev{ + c: c, + maxTxSize: maxTxSize, + dc: dc, + r: reset, + busy: busy, + color: o.ModelColor, + border: o.BorderColor, + model: o.Model, + variant: o.DisplayVariant, + pcbVariant: o.PCBVariant, + }, + saturation: 50, // Looks good enough for most of the images. + } + + switch o.Model { + case IMPRESSION4: + d.width = 640 + d.height = 400 + d.res = 0b10 + case IMPRESSION57: + d.width = 600 + d.height = 448 + d.res = 0b11 + } + // Prefer the passed in values via Opts. + if o.Width == 0 && o.Height == 0 { + d.width = o.Width + d.height = o.Height + } + d.bounds = image.Rect(0, 0, d.width, d.height) + + d.Pix = make([]uint8, d.height*d.width) + + return d, nil +} + +// blend recalculates the palette based on the saturation level. +func (d *DevImpression) blend() []color.Color { + sat := float64(d.saturation / 100) + + pr := []color.Color{} + for i := 0; i < 7; i++ { + rs, gs, bs := + uint8(float64(sc[i].R)*sat), + uint8(float64(sc[i].G)*sat), + uint8(float64(sc[i].B)*sat) + + rd, gd, bd := + uint8(float64(dsc[i].R)*(1.0-sat)), + uint8(float64(dsc[i].G)*(1.0-sat)), + uint8(float64(dsc[i].B)*(1.0-sat)) + + pr = append(pr, color.RGBA{rs + rd, gs + gd, bs + bd, dsc[i].A}) + } + // Add Transparent color and return the result. + return append(pr, color.RGBA{255, 255, 255, 0}) +} + +// Saturation returns the current saturation level. +func (d *DevImpression) Saturation() uint { + return d.saturation +} + +// SetSaturaton changes the saturation level. This will not take effect until the next Draw(). +func (d *DevImpression) SetSaturation(level uint) error { + if level > 100 { + return fmt.Errorf("saturation level needs to be between 0 and 100") + } + d.saturation = level + // so that caller can recalculate next time they need it. + d.Palette = nil + + return nil +} + +// SetBorder changes the border color. This will not take effect until the next Draw(). +func (d *DevImpression) SetBorder(c ImpressionColor) { + d.border = Color(c) +} + +// Render renders the content of the Pix to the screen. +func (d *DevImpression) Render() error { + if d.flipVertically { + for w := 0; w < len(d.Pix)/2-1; w = w + d.width { + for offset := 0; offset < d.width; offset++ { + d.Pix[w+offset], d.Pix[len(d.Pix)-d.width-w+offset] = d.Pix[len(d.Pix)-d.width-w+offset], d.Pix[w+offset] + } + } + } + + if d.flipHorizontally { + for offset := 0; offset < len(d.Pix)-1; offset = offset + d.width { + for i, j := 0, d.width-1; i < j; i, j = i+1, j-1 { + d.Pix[i+offset], d.Pix[j+offset] = d.Pix[j+offset], d.Pix[i+offset] + } + } + } + + merged := make([]uint8, len(d.Pix)/2) + for i, offset := 0, 0; i < len(d.Pix)-1; i, offset = i+2, offset+1 { + merged[offset] = (d.Pix[i]<<4)&0xF0 | d.Pix[i+1]&0x0F + } + + return d.update(merged) +} + +func (d *DevImpression) reset() error { + if err := d.r.Out(gpio.Low); err != nil { + return err + } + time.Sleep(100 * time.Millisecond) + if err := d.r.Out(gpio.High); err != nil { + return err + } + d.wait(1 * time.Second) + + // Resolution Setting + // 10bit horizontal followed by a 10bit vertical resolution + tres := make([]byte, 4) + binary.LittleEndian.PutUint16(tres[0:], uint16(d.width)) + binary.LittleEndian.PutUint16(tres[2:], uint16(d.height)) + + if err := d.sendCommand(uc8159TRES, tres); err != nil { + return err + } + + // Panel Setting + // 0b11000000 = Resolution select, 0b00 = 640x480, our panel is 0b11 = 600x448 + // 0b00100000 = LUT selection, 0 = ext flash, 1 = registers, we use ext flash + // 0b00010000 = Ignore + // 0b00001000 = Gate scan direction, 0 = down, 1 = up (default) + // 0b00000100 = Source shift direction, 0 = left, 1 = right (default) + // 0b00000010 = DC-DC converter, 0 = off, 1 = on + // 0b00000001 = Soft reset, 0 = Reset, 1 = Normal (Default) + // 0b11 = 600x448 + // 0b10 = 640x400 + if err := d.sendCommand( + uc8159PSR, + []byte{ + byte(d.res<<6) | 0b101111, // See above for more magic numbers + 0x08, // display_colours == UC81597C + }); err != nil { + return err + } + + // Power Settings + if err := d.sendCommand( + uc8159PWR, + []byte{ + (0x06 << 3) | // ??? - not documented in UC8159 datasheet + (0x01 << 2) | // SOURCE_INTERNAL_DC_DC + (0x01 << 1) | // GATE_INTERNAL_DC_DC + (0x01), // LV_SOURCE_INTERNAL_DC_DC + 0x00, // VGx_20V + 0x23, // UC81597C + 0x23, // UC81597C + }); err != nil { + return err + } + + // Set the PLL clock frequency to 50Hz + // 0b11000000 = Ignore + // 0b00111000 = M + // 0b00000111 = N + // PLL = 2MHz * (M / N) + // PLL = 2MHz * (7 / 4) + // PLL = 2,800,000 ??? + if err := d.sendCommand(uc8159PLL, []byte{0x3C}); err != nil { + return err + } + // 0b00111100 + // Send the TSE register to the display + if err := d.sendCommand(uc8159TSE, []byte{0x00}); err != nil { // Color + return err + } + // VCOM and Data Interval setting + // 0b11100000 = Vborder control (0b001 = LUTB voltage) + // 0b00010000 = Data polarity + // 0b00001111 = Vcom and data interval (0b0111 = 10, default) + + cdi := make([]byte, 2) + binary.LittleEndian.PutUint16(cdi[0:], uint16(d.border<<5)|0x17) // 0b00110111 + if err := d.sendCommand(uc8159CDI, cdi); err != nil { + return err + } + + // Gate/Source non-overlap period + // 0b11110000 = Source to Gate (0b0010 = 12nS, default) + // 0b00001111 = Gate to Source + if err := d.sendCommand(uc8159TCON, []byte{0x22}); err != nil { // 0b00100010 + return err + } + + // Disable external flash + if err := d.sendCommand(uc8159DAM, []byte{0x00}); err != nil { + return err + } + + // UC81597C + if err := d.sendCommand(uc8159PWS, []byte{0xAA}); err != nil { + return err + } + + // Power off sequence + // 0b00110000 = power off sequence of VDH and VDL, 0b00 = 1 frame (default) + // All other bits ignored? + if err := d.sendCommand(uc8159PFS, []byte{0x00}); err != nil { // PFS_1_FRAME + return err + } + + return nil +} + +func (d *DevImpression) update(pix []uint8) error { + if err := d.reset(); err != nil { + return err + } + + if err := d.sendCommand(uc8159DTM1, pix); err != nil { + return err + } + + if err := d.sendCommand(uc8159PON, nil); err != nil { + return err + } + d.wait(200 * time.Millisecond) + + if err := d.sendCommand(uc8159DRF, nil); err != nil { + return err + } + d.wait(32 * time.Second) + + if err := d.sendCommand(uc8159POF, nil); err != nil { + return err + } + d.wait(200 * time.Millisecond) + + return nil +} + +// Wait for busy/wait pin. +func (d *DevImpression) wait(dur time.Duration) { + // Set it as input, with a pull down and enable rising edge triggering. + if err := d.busy.In(gpio.PullDown, gpio.RisingEdge); err != nil { + log.Printf("Err: %s", err) + return + } + // Wait for rising edges (Low -> High) or the timeout. + d.busy.WaitForEdge(dur) +} + +// ColorModel returns the device native color model. +func (d *DevImpression) ColorModel() color.Model { + if d.Palette == nil { + d.Palette = d.blend() + } + return d.Palette +} + +// At returns the color of the pixel at (x, y). +func (d *DevImpression) At(x, y int) color.Color { + if d.Palette == nil { + d.Palette = d.blend() + } + return d.Palette[d.Pix[y*d.width+x]] +} + +// Set sets the pixel at (x, y) to the given color. This will not take effect until the next Draw(). +func (d *DevImpression) Set(x, y int, c color.Color) { + if d.Palette == nil { + d.Palette = d.blend() + } + d.Pix[y*d.width+x] = uint8(d.Palette.Index(c)) +} + +// Draw updates the display with the image. +func (d *DevImpression) Draw(r image.Rectangle, src image.Image, sp image.Point) error { + if r != d.Bounds() { + return fmt.Errorf("partial updates are not supported") + } + + if src.Bounds() != d.Bounds() { + return fmt.Errorf("image must be the same size as bounds: %v", d.Bounds()) + } + + // Dither the image using Floyd–Steinberg dithering algorithm otherwise it won't look as good on the screen. + draw.FloydSteinberg.Draw(d, r, src, image.Point{}) + return d.Render() +} + +// DrawAll redraws the whole display. +func (d *DevImpression) DrawAll(src image.Image) error { + return d.Draw(d.Bounds(), src, image.Point{}) +} diff --git a/inky/inky.go b/inky/inky.go index c746a6d..9fcd60c 100644 --- a/inky/inky.go +++ b/inky/inky.go @@ -14,142 +14,17 @@ import ( "periph.io/x/conn/v3" "periph.io/x/conn/v3/display" "periph.io/x/conn/v3/gpio" - "periph.io/x/conn/v3/i2c" "periph.io/x/conn/v3/physic" "periph.io/x/conn/v3/spi" ) -// Color is used to define which model of inky is being used, and also for -// setting the border color. -type Color int - -// Valid Color. -const ( - Black Color = iota - Red - Yellow - White -) - -func (c *Color) String() string { - switch *c { - case Black: - return "black" - case Red: - return "red" - case Yellow: - return "yellow" - case White: - return "white" - default: - return "unknown" - } -} - -// Set sets the Color to a value represented by the string s. Set implements the flag.Value interface. -func (c *Color) Set(s string) error { - switch s { - case "black": - *c = Black - case "red": - *c = Red - case "yellow": - *c = Yellow - case "white": - *c = White - default: - return fmt.Errorf("unknown color %q: expected either black, red, yellow or white", s) - } - return nil -} - -// Model lists the supported e-ink display models. -type Model int +var _ display.Drawer = &Dev{} +var _ conn.Resource = &Dev{} -// Supported Model. const ( - PHAT Model = iota - WHAT - PHAT2 + cs0Pin = 8 ) -func (m *Model) String() string { - switch *m { - case PHAT: - return "PHAT" - case PHAT2: - return "PHAT2" - case WHAT: - return "WHAT" - default: - return "Unknown" - } -} - -// Set sets the Model to a value represented by the string s. Set implements the flag.Value interface. -func (m *Model) Set(s string) error { - switch s { - case "PHAT": - *m = PHAT - case "PHAT2": - *m = PHAT2 - case "WHAT": - *m = WHAT - default: - return fmt.Errorf("unknown model %q: expected either PHAT or WHAT", s) - } - return nil -} - -// Opts is the options to specify which device is being controlled and its -// default settings. -type Opts struct { - // Model being used. - Model Model - // Model color. - ModelColor Color - // Initial border color. Will be set on the first Draw(). - BorderColor Color -} - -// DetectOpts tries to read the device opts from EEPROM. -func DetectOpts(bus i2c.Bus) (*Opts, error) { - // Read data from EEPROM - data, err := readEep(bus) - if err != nil { - return nil, fmt.Errorf("failed to detect Inky board: %v", err) - } - - options := new(Opts) - - switch data[6] { - case 1, 4, 5: - options.Model = PHAT - case 10, 11, 12: - options.Model = PHAT2 - case 2, 3, 6, 7, 8: - options.Model = WHAT - default: - return nil, fmt.Errorf("failed to get ops: display type not supported") - } - - switch data[4] { - case 1: - options.ModelColor = Black - options.BorderColor = Black - case 2: - options.ModelColor = Red - options.BorderColor = Red - case 3: - options.ModelColor = Yellow - options.BorderColor = Yellow - default: - return nil, fmt.Errorf("failed to get ops: color not supported") - } - - return options, nil -} - var borderColor = map[Color]byte{ Black: 0x00, Red: 0x73, @@ -157,13 +32,48 @@ var borderColor = map[Color]byte{ White: 0x31, } +// Dev is a handle to an Inky. +type Dev struct { + c conn.Conn + // Maximum number of bytes allowed to be sent as a single I/O on c. + maxTxSize int + // Low when sending a command, high when sending data. + dc gpio.PinOut + // Reset pin, active low. + r gpio.PinOut + // High when device is busy. + busy gpio.PinIn + // Size of this model's display. + bounds image.Rectangle + // Whether this model needs the image flipped vertically. + flipVertically bool + // Whether this model needs the image flipped horizontally. + flipHorizontally bool + // Color of device screen (red, yellow or black). + color Color + // Modifiable color of border. + border Color + + // Width of the panel. + width int + // Height of the panel. + height int + + // Model being used. + model Model + // Variant of the panel. + variant uint + // PCB Variant of the panel. Represents a version string as a number (12 -> 1.2). + pcbVariant uint +} + // New opens a handle to an Inky pHAT or wHAT. func New(p spi.Port, dc gpio.PinOut, reset gpio.PinOut, busy gpio.PinIn, o *Opts) (*Dev, error) { if o.ModelColor != Black && o.ModelColor != Red && o.ModelColor != Yellow { return nil, fmt.Errorf("unsupported color: %v", o.ModelColor) } - c, err := p.Connect(488*physic.KiloHertz, spi.Mode0, 8) + c, err := p.Connect(488*physic.KiloHertz, spi.Mode0, cs0Pin) if err != nil { return nil, fmt.Errorf("failed to connect to inky over spi: %v", err) } @@ -179,51 +89,40 @@ func New(p spi.Port, dc gpio.PinOut, reset gpio.PinOut, busy gpio.PinIn, o *Opts } d := &Dev{ - c: c, - maxTxSize: maxTxSize, - dc: dc, - r: reset, - busy: busy, - color: o.ModelColor, - border: o.BorderColor, + c: c, + maxTxSize: maxTxSize, + dc: dc, + r: reset, + busy: busy, + color: o.ModelColor, + border: o.BorderColor, + model: o.Model, + variant: o.DisplayVariant, + pcbVariant: o.PCBVariant, } switch o.Model { case PHAT: - d.bounds = image.Rect(0, 0, 104, 212) + d.width = 104 + d.height = 212 d.flipVertically = true case PHAT2: - d.bounds = image.Rect(0, 0, 122, 250) + d.width = 122 + d.height = 250 d.flipVertically = true case WHAT: - d.bounds = image.Rect(0, 0, 400, 300) + d.width = 400 + d.height = 300 } - + // Prefer the passed in values via Opts. + if o.Width == 0 && o.Height == 0 { + d.width = o.Width + d.height = o.Height + } + d.bounds = image.Rect(0, 0, d.width, d.height) return d, nil } -// Dev is a handle to an Inky. -type Dev struct { - c conn.Conn - // Maximum number of bytes allowed to be sent as a single I/O on c. - maxTxSize int - // Low when sending a command, high when sending data. - dc gpio.PinOut - // Reset pin, active low. - r gpio.PinOut - // High when device is busy. - busy gpio.PinIn - // Size of this model's display. - bounds image.Rectangle - // Whether this model needs the image flipped vertically. - flipVertically bool - - // Color of device screen (red, yellow or black). - color Color - // Modifiable color of border. - border Color -} - // SetBorder changes the border color. This will not take effect until the next Draw(). func (d *Dev) SetBorder(c Color) { d.border = c @@ -241,10 +140,32 @@ func (d *Dev) SetModelColor(c Color) error { // String implements conn.Resource. func (d *Dev) String() string { + index := int(d.variant) + if index < len(displayVariantMap) { + return displayVariantMap[index] + } return "Inky pHAT" } -// Halt implements conn.Resource +func (d *Dev) Height() int { + return d.height +} + +func (d *Dev) Width() int { + return d.width +} + +// SetFlipVertically flips the image horizontally. +func (d *Dev) SetFlipVertically(f bool) { + d.flipVertically = f +} + +// SetFlipHorizontally flips the image horizontally. +func (d *Dev) SetFlipHorizontally(f bool) { + d.flipHorizontally = f +} + +// Halt implements conn.Resource. func (d *Dev) Halt() error { return nil } @@ -461,19 +382,3 @@ func pack(bits []bool) ([]byte, error) { } return ret, nil } - -func readEep(bus i2c.Bus) ([]byte, error) { - // Inky uses SMBus, specify read registry with data - write := []byte{0x00, 0x00} - - data := make([]byte, 29) - - if err := bus.Tx(0x50, write, data); err != nil { - return nil, err - } - - return data, nil -} - -var _ display.Drawer = &Dev{} -var _ conn.Resource = &Dev{} diff --git a/inky/opts.go b/inky/opts.go new file mode 100644 index 0000000..d7ecc3b --- /dev/null +++ b/inky/opts.go @@ -0,0 +1,120 @@ +// Copyright 2023 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 inky + +import ( + "encoding/binary" + "fmt" + + "periph.io/x/conn/v3/i2c" +) + +var ( + displayVariantMap = [...]string{ + "", + "Red pHAT (High-Temp)", + "Yellow wHAT", + "Black wHAT", + "Black pHAT", + "Yellow pHAT", + "Red wHAT", + "Red wHAT (High-Temp)", + "Red wHAT", + "", + "Black pHAT (SSD1608)", + "Red pHAT (SSD1608)", + "Yellow pHAT (SSD1608)", + "", + "7-Colour (UC8159)", + "7-Colour 640x400 (UC8159)", + "7-Colour 640x400 (UC8159)", + "Black wHAT (SSD1683)", + "Red wHAT (SSD1683)", + "Yellow wHAT (SSD1683)", + } +) + +// Opts is the options to specify which device is being controlled and its +// default settings. +type Opts struct { + // Boards's width and height. + Width int + Height int + + // Model being used. + Model Model + // Model color. + ModelColor Color + // Initial border color. Will be set on the first Draw(). + BorderColor Color + + // Board information. + PCBVariant uint + DisplayVariant uint +} + +// DetectOpts tries to read the device opts from EEPROM. +func DetectOpts(bus i2c.Bus) (*Opts, error) { + // Read data from EEPROM + data, err := readEep(bus) + if err != nil { + return nil, fmt.Errorf("failed to detect Inky board: %v", err) + } + options := new(Opts) + + options.Width = int(binary.LittleEndian.Uint16(data[0:])) + options.Height = int(binary.LittleEndian.Uint16(data[2:])) + + switch data[4] { + case 1: + options.ModelColor = Black + options.BorderColor = Black + case 2: + options.ModelColor = Red + options.BorderColor = Red + case 3: + options.ModelColor = Yellow + options.BorderColor = Yellow + case 4: + options.ModelColor = Multi + options.BorderColor = Color(WhiteImpression) + default: + return nil, fmt.Errorf("failed to get ops: color %v not supported", data[4]) + } + // PCB Variant is stored as a number in the eeprom but is actually corresponds a version string (12 -> 1.2) + options.PCBVariant = uint(data[5]) + + switch data[6] { + case 1, 4, 5: + options.Model = PHAT + case 10, 11, 12: + options.Model = PHAT2 + case 2, 3, 6, 7, 8: + options.Model = WHAT + case 14: + options.Model = IMPRESSION57 + case 15, 16: + options.Model = IMPRESSION4 + default: + return nil, fmt.Errorf("failed to get ops: display type %v not supported", data[6]) + } + + options.DisplayVariant = uint(data[6]) + + return options, nil +} + +func readEep(bus i2c.Bus) ([]byte, error) { + // Inky uses SMBus, specify read registry with data + write := []byte{0x00, 0x00} + + data := make([]byte, 29) + + if err := bus.Tx(0x50, write, data); err != nil { + return nil, err + } + + return data, nil +} diff --git a/inky/types.go b/inky/types.go new file mode 100644 index 0000000..b5ac48b --- /dev/null +++ b/inky/types.go @@ -0,0 +1,111 @@ +// Copyright 2023 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 inky + +//go:generate stringer -type=Model,Color,ImpressionColor -output types_string.go + +import ( + "fmt" +) + +// Model lists the supported e-ink display models. +type Model int + +// Supported Model. +const ( + PHAT Model = iota + WHAT + PHAT2 + IMPRESSION4 + IMPRESSION57 +) + +// Set sets the Model to a value represented by the string s. Set implements the flag.Value interface. +func (m *Model) Set(s string) error { + switch s { + case "PHAT": + *m = PHAT + case "PHAT2": + *m = PHAT2 + case "WHAT": + *m = WHAT + case "IMPRESSION4": + *m = IMPRESSION4 + case "IMPRESSION57": + *m = IMPRESSION57 + default: + return fmt.Errorf("unknown model %q: expected PHAT, PHAT2, WHAT, IMPRESSION4 or IMPRESSION57", s) + } + return nil +} + +// Color is used to define which model of inky is being used, and also for +// setting the border color. +type Color int + +// Valid Color. +const ( + Black Color = iota + Red + Yellow + White + Multi +) + +// Set sets the Color to a value represented by the string s. Set implements the flag.Value interface. +func (c *Color) Set(s string) error { + switch s { + case "black": + *c = Black + case "red": + *c = Red + case "yellow": + *c = Yellow + case "white": + *c = White + default: + return fmt.Errorf("unknown color %q: expected either black, red, yellow or white", s) + } + return nil +} + +// ImpressionColor is used to define colors used by Inky Impression models. +type ImpressionColor uint8 + +const ( + BlackImpression ImpressionColor = iota + WhiteImpression + GreenImpression + BlueImpression + RedImpression + YellowImpression + OrangeImpression + CleanImpression +) + +// Set sets the ImpressionColor to a value represented by the string s. Set implements the flag.Value interface. +func (c *ImpressionColor) Set(s string) error { + switch s { + case "black": + *c = BlackImpression + case "white": + *c = WhiteImpression + case "green": + *c = GreenImpression + case "blue": + *c = BlueImpression + case "red": + *c = RedImpression + case "yellow": + *c = YellowImpression + case "orange": + *c = OrangeImpression + case "clean": + *c = CleanImpression + default: + return fmt.Errorf("unknown color %q: expected either black, white. green, blue, red, yellow, orange or clean", s) + } + return nil +} diff --git a/inky/types_string.go b/inky/types_string.go new file mode 100644 index 0000000..a5f1ea2 --- /dev/null +++ b/inky/types_string.go @@ -0,0 +1,72 @@ +// Code generated by "stringer -type=Model,Color,ImpressionColor -output types_string.go"; DO NOT EDIT. + +package inky + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[PHAT-0] + _ = x[WHAT-1] + _ = x[PHAT2-2] + _ = x[IMPRESSION4-3] + _ = x[IMPRESSION57-4] +} + +const _Model_name = "PHATWHATPHAT2IMPRESSION4IMPRESSION57" + +var _Model_index = [...]uint8{0, 4, 8, 13, 24, 36} + +func (i Model) String() string { + if i < 0 || i >= Model(len(_Model_index)-1) { + return "Model(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _Model_name[_Model_index[i]:_Model_index[i+1]] +} +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[Black-0] + _ = x[Red-1] + _ = x[Yellow-2] + _ = x[White-3] + _ = x[Multi-4] +} + +const _Color_name = "BlackRedYellowWhiteMulti" + +var _Color_index = [...]uint8{0, 5, 8, 14, 19, 24} + +func (i Color) String() string { + if i < 0 || i >= Color(len(_Color_index)-1) { + return "Color(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _Color_name[_Color_index[i]:_Color_index[i+1]] +} +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[BlackImpression-0] + _ = x[WhiteImpression-1] + _ = x[GreenImpression-2] + _ = x[BlueImpression-3] + _ = x[RedImpression-4] + _ = x[YellowImpression-5] + _ = x[OrangeImpression-6] + _ = x[CleanImpression-7] +} + +const _ImpressionColor_name = "BlackImpressionWhiteImpressionGreenImpressionBlueImpressionRedImpressionYellowImpressionOrangeImpressionCleanImpression" + +var _ImpressionColor_index = [...]uint8{0, 15, 30, 45, 59, 72, 88, 104, 119} + +func (i ImpressionColor) String() string { + if i >= ImpressionColor(len(_ImpressionColor_index)-1) { + return "ImpressionColor(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _ImpressionColor_name[_ImpressionColor_index[i]:_ImpressionColor_index[i+1]] +}