package main import ( "context" "fmt" "image/color" "log" "net/http" "sync" "time" "git.dvdt.dev/david/bitcoin-ticker-pi/display" "git.dvdt.dev/david/bitcoin-ticker-pi/fonts" rgbmatrix "git.dvdt.dev/david/bitcoin-ticker-pi/go-rpi-rgb-led-matrix" "git.dvdt.dev/david/bitcoin-ticker-pi/mempool" ) const ( RefreshRateHz = 120 MempoolURL = "mempool.space" ) func main() { cfg := rgbmatrix.DefaultConfig cfg.Rows = 32 cfg.Cols = 64 m, _ := rgbmatrix.NewRGBLedMatrix(&cfg) c := rgbmatrix.NewCanvas(m) d := display.New(c, display.NewSlot(display.Left, 9, 5, 4, 0, 0), display.NewSlot(display.Right, 5, 5, 4, 44, 0), display.NewSlot(display.Center, 8, 13, 8, 0, 9), display.NewSlot(display.Center, 16, 5, 4, 0, 27), ) ctx, cancel := context.WithCancel(context.Background()) defer cancel() ticker := time.NewTicker(time.Second / RefreshRateHz) tdone := make(chan struct{}) mdone := make(chan struct{}) msgs := make(chan mempool.Message) errs := make(chan error) mp := mempool.New(MempoolURL, &http.Client{ Timeout: 5 * time.Second, }) err := mp.Init() if err != nil { log.Fatal(err) } currentBlockHeight, err := mp.CurrentBlockHeight() if err != nil { log.Fatal(err) } highfee, medfee, lowfee, err := mp.CurrentFees() if err != nil { log.Fatal(err) } currentPrice, err := mp.CurrentPrice() if err != nil { log.Fatal(err) } var blockHeight int blockHeightMutex := &sync.Mutex{} var fees mempool.Fees feesMutex := &sync.Mutex{} var price int priceMutex := &sync.Mutex{} blockHeight = currentBlockHeight fees.FastestFee = highfee fees.HalfHourFee = medfee fees.HourFee = lowfee price = currentPrice go mp.Listen(ctx, msgs, errs, mdone) go func() { for { select { case m := <-msgs: if m.Fees != nil { feesMutex.Lock() fees = *m.Fees feesMutex.Unlock() } if m.Block != nil { blockHeightMutex.Lock() blockHeight = int(m.Block.Height) blockHeightMutex.Unlock() } if m.Conversions != nil { priceMutex.Lock() price = m.Conversions.USD priceMutex.Unlock() } } } }() for { select { case <-tdone: return case <-ticker.C: d.Clear() err = updateSlots(d, price, blockHeight, fees.HourFee, fees.HalfHourFee, fees.FastestFee) if err != nil { log.Fatal(err) } } } } func updateSlots(d *display.Display, price, height, lfee, mfee, hfee int) error { d.Clear() var priceSegments []display.Segment priceSegments = append(priceSegments, display.Segment{Text: "dollar", Color: fonts.Green}) priceSegments = append(priceSegments, strToDisplaySegments(fmt.Sprintf("%d", price), fonts.White)...) err := d.UpdateTopLeft(priceSegments) if err != nil { return err } var moscowTimeSegments []display.Segment moscowTime := 0 if price > 0 { moscowTime = 100000000 / price } moscowTimeSegments = append(moscowTimeSegments, display.Segment{Text: "satoshi", Color: fonts.Orange}) moscowTimeSegments = append(moscowTimeSegments, strToDisplaySegments(fmt.Sprintf("%d", moscowTime), fonts.White)...) err = d.UpdateTopRight(moscowTimeSegments) if err != nil { return err } var heightSegments []display.Segment heightSegments = append(heightSegments, display.Segment{Text: "height", Color: fonts.Blue}) heightSegments = append(heightSegments, strToDisplaySegments(fmt.Sprintf("%d", height), fonts.White)...) err = d.UpdateMain(heightSegments) if err != nil { return err } var feeSegments []display.Segment feeSegments = append(feeSegments, display.Segment{Text: "low", Color: fonts.Green}) feeSegments = append(feeSegments, strToDisplaySegments(fmt.Sprintf("%d", lfee), fonts.White)...) feeSegments = append(feeSegments, display.Segment{Text: "medium", Color: fonts.Orange}) feeSegments = append(feeSegments, strToDisplaySegments(fmt.Sprintf("%d", mfee), fonts.White)...) feeSegments = append(feeSegments, display.Segment{Text: "high", Color: fonts.Red}) feeSegments = append(feeSegments, strToDisplaySegments(fmt.Sprintf("%d", hfee), fonts.White)...) err = d.UpdateBottom(feeSegments) if err != nil { return err } return d.Render() } func strToDisplaySegments(s string, c color.RGBA) []display.Segment { var segs []display.Segment for _, r := range s { segs = append(segs, display.Segment{Text: string(r), Color: c}) } return segs }