URI: 
       tticker.py - btcticker - eInk Bitcoin price ticker
  HTML git clone https://git.parazyd.org/btcticker
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
       tticker.py (7460B)
       ---
            1 #!/usr/bin/env python3
            2 # Copyright (c) 2021 parazyd
            3 # Copyright (c) 2020 llvll
            4 #
            5 # Permission is hereby granted, free of charge, to any person obtaining a copy
            6 # of this software and associated documentation files (the "Software"), to deal
            7 # in the Software without restriction, including without limitation the rights
            8 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
            9 # copies of the Software, and to permit persons to whom the Software is
           10 # furnished to do so, subject to the following conditions:
           11 #
           12 # The above copyright notice and this permission notice shall be included in
           13 # all copies or substantial portions of the Software.
           14 #
           15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
           16 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
           17 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
           18 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
           19 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
           20 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
           21 # SOFTWARE.
           22 """
           23 Program to create a bitcoin ticker bitmap and show it on a waveshare 2in13_V2
           24 eink screen (https://www.waveshare.com/wiki/2.13inch_e-Paper_HAT)
           25 """
           26 
           27 import sys
           28 from io import BytesIO
           29 from json.decoder import JSONDecodeError
           30 from os.path import join, dirname, realpath
           31 from time import time, strftime, sleep
           32 import matplotlib as mpl
           33 import matplotlib.pyplot as plt
           34 import numpy as np
           35 from PIL import Image, ImageFont, ImageDraw
           36 import requests
           37 
           38 mpl.use('Agg')
           39 
           40 picdir = join(dirname(realpath(__file__)), 'images')
           41 fontdir = join(dirname(realpath(__file__)), 'fonts')
           42 font = ImageFont.truetype(join(fontdir, 'googlefonts/Roboto-Medium.ttf'), 40)
           43 font_date = ImageFont.truetype(join(fontdir, 'PixelSplitter-Bold.ttf'), 11)
           44 
           45 pairs = [
           46     {
           47         "currency": "usd",
           48         "coin": "bitcoin",
           49         "image": Image.open(join(picdir, "currency/bitcoin.png")),
           50     },
           51     {
           52         "currency": "usd",
           53         "coin": "dogecoin",
           54         "image": Image.open(join(picdir, "currency/dogecoin.png")),
           55     },
           56     {
           57         "currency": "usd",
           58         "coin": "ethereum",
           59         "image": Image.open(join(picdir, "currency/ethereum.png")),
           60     },
           61     {
           62         "currency": "usd",
           63         "coin": "litecoin",
           64         "image": Image.open(join(picdir, "currency/litecoin.png")),
           65     },
           66     {
           67         "currency": "usd",
           68         "coin": "monero",
           69         "image": Image.open(join(picdir, "currency/monero.png")),
           70     },
           71     {
           72         "currency": "usd",
           73         "coin": "solana",
           74         "image": Image.open(join(picdir, "currency/solana.png")),
           75     },
           76     {
           77         "currency": "usd",
           78         "coin": "zcash",
           79         "image": Image.open(join(picdir, "currency/zcash.png")),
           80     },
           81 ]
           82 
           83 athbitmap = Image.open(join(picdir, 'ATH.bmp'))
           84 API = 'https://api.coingecko.com/api/v3/coins'
           85 
           86 
           87 def get_data(pair, other):
           88     """ Grab data from API """
           89     days_ago = 7
           90     endtime = int(time())
           91     starttime = endtime - 60 * 60 * 24 * days_ago
           92 
           93     geckourl = '%s/markets?vs_currency=%s&ids=%s' % (API, pair["currency"],
           94                                                      pair["coin"])
           95     liveprice = requests.get(geckourl).json()[0]
           96     pricenow = float(liveprice['current_price'])
           97     alltimehigh = float(liveprice['ath'])
           98     other['volume'] = float(liveprice['total_volume'])
           99 
          100     url_hist = '%s/%s/market_chart/range?vs_currency=%s&from=%s&to=%s' % (
          101         API, pair["coin"], pair["currency"], str(starttime), str(endtime))
          102 
          103     try:
          104         timeseriesarray = requests.get(url_hist).json()['prices']
          105     except JSONDecodeError as err:
          106         print(f'Caught JSONDecodeError: {repr(err)}')
          107         return None
          108     timeseriesstack = []
          109     length = len(timeseriesarray)
          110     i = 0
          111     while i < length:
          112         timeseriesstack.append(float(timeseriesarray[i][1]))
          113         i += 1
          114 
          115     timeseriesstack.append(pricenow)
          116     if pricenow > alltimehigh:
          117         other['ATH'] = True
          118     else:
          119         other['ATH'] = False
          120 
          121     other["image"] = pair["image"]
          122     other["coin"] = pair["coin"]
          123 
          124     return timeseriesstack
          125 
          126 
          127 def make_spark(pricestack):
          128     """ Make a historical plot """
          129     _x = pricestack - np.mean(pricestack)
          130     fig, _ax = plt.subplots(1, 1, figsize=(10, 3))
          131     plt.plot(_x, color='k', linewidth=6)
          132     plt.plot(len(_x) - 1, _x[-1], color='r', marker='o')
          133 
          134     for _, i in _ax.spines.items():
          135         i.set_visible(False)
          136     _ax.set_xticks = ([])
          137     _ax.set_yticks = ([])
          138     _ax.axhline(c='k', linewidth=4, linestyle=(0, (5, 2, 1, 2)))
          139 
          140     buf = BytesIO()
          141     plt.savefig(buf, format='png', dpi=17)
          142     buf.seek(0)
          143     imgspk = Image.open(buf)
          144 
          145     plt.clf()
          146     _ax.cla()
          147     plt.close(fig)
          148     return imgspk
          149 
          150 
          151 def update_display(pricestack, sparkimage, other):
          152     """ Create an image from the data and send it to the display """
          153     days_ago = 7
          154     pricenow = pricestack[-1]
          155 
          156     if not other.get('lastprice'):
          157         other['lastprice'] = pricenow
          158     elif pricenow == other['lastprice']:
          159         return other
          160 
          161     other['lastprice'] = pricenow
          162 
          163     pricechange = str('%+d' % round(
          164         (pricestack[-1] - pricestack[0]) / pricestack[-1] * 100, 2)) + '%'
          165     if pricenow > 1000:
          166         pricenowstring = format(int(pricenow), ',')
          167     else:
          168         pricenowstring = str(float('%.5g' % pricenow))
          169 
          170     image = Image.new('L', (250, 122), 255)
          171     draw = ImageDraw.Draw(image)
          172     if other['ATH'] is True:
          173         print(f"{other['coin']}: {pricenowstring} (ATH!)")
          174         image.paste(athbitmap, (15, 30))
          175     else:
          176         print(f"{other['coin']}: {pricenowstring}")
          177         image.paste(other["image"], (5, 12))
          178 
          179     draw.text((8, 92), other["coin"], font=font_date, fill=0)
          180 
          181     image.paste(sparkimage, (80, 15))
          182     draw.text((130, 66),
          183               str(days_ago) + 'day : ' + pricechange,
          184               font=font_date,
          185               fill=0)
          186     draw.text((96, 73), '$' + pricenowstring, font=font, fill=0)
          187 
          188     draw.text((95, 5),
          189               str(strftime('%H:%M %a %d %b %Y')),
          190               font=font_date,
          191               fill=0)
          192 
          193     display_eink(image)
          194 
          195     return other
          196 
          197 
          198 def display_eink(image):
          199     """ Wrapper to display or show image """
          200     if epd:
          201         epd.display(epd.getbuffer(image))
          202     else:
          203         image.show()
          204 
          205 
          206 def close_epd():
          207     """ Cleanup for epd context """
          208     if not epd:
          209         return
          210     epd.sleep()
          211     epd.Dev_exit()
          212     # epd2in13_V2.epdconfig.module_exit()
          213 
          214 
          215 def main():
          216     """ main routine """
          217 
          218     def fullupdate(pair):
          219         other = {}
          220         pricestack = get_data(pair, other)
          221         if not pricestack:
          222             return time()
          223         sparkimage = make_spark(pricestack)
          224         other = update_display(pricestack, sparkimage, other)
          225         return time()
          226 
          227     try:
          228         data_pulled = False
          229         lastcoinfetch = time()
          230 
          231         while True:
          232             for i in pairs:
          233                 if (time() - lastcoinfetch > float(40)) or data_pulled is False:
          234                     lastcoinfetch = fullupdate(i)
          235                     data_pulled = True
          236                 sleep(5)
          237     except KeyboardInterrupt:
          238         close_epd()
          239         return 1
          240 
          241     return 1
          242 
          243 
          244 if __name__ == '__main__':
          245     if len(sys.argv) > 1 and sys.argv[1] == '-d':
          246         epd = None  # pylint: disable=C0103
          247     else:
          248         from waveshare_epd import epd2in13_V2  # pylint: disable=E0401
          249         epd = epd2in13_V2.EPD()
          250         epd.init(epd.FULL_UPDATE)
          251         epd.Clear(0xFF)
          252     sys.exit(main())