#!/usr/bin/python
# -*- encoding: utf-8 -*-

import time    
import RPi.GPIO as GPIO  # Prime ovladani GPIO v RasPi
import math              # vyuziva se jen v prikladech pri kresleni kruznic
import random



# prirazeni GPIO pinu na signaly displeje
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BOARD)   # cislovani podle GPIO pinu (1 az 26)

pin_res = 16  # reset na GPIO23 (pin 16) -> displej vyvod 1 (RST)
pin_rs  = 13  # registry select na GPIO27R2 (pin 13) -> displej vyvod 3 (D/C)
pin_sda = 12  # seriova DATA na GPIO18 (pin 12) -> displej vyvod 4
pin_scl = 11  # hodiny na GPIO17 (pin 11) -> displej noha 5

#     ostatni vyvody displeje:
# Displej 2 ... CE  ... GND   = (GPIO pin 6)
# Displej 6 ... Vcc ... +3,3V = (GPIO pin 1)
# Displej 7 ... LED ... GND   = (GPIO pin 6)
# Displej 8 ... GND ... GND   = (GPIO pin 6)

# nastaveni smeru GPIO pro displeje
GPIO.setup(pin_res, GPIO.OUT) 
GPIO.setup(pin_rs , GPIO.OUT) 
GPIO.setup(pin_sda, GPIO.OUT) 
GPIO.setup(pin_scl, GPIO.OUT) 

# uvodni nastaveni vsech signalu na "0" (mimo resetu)
GPIO.output(pin_scl, GPIO.LOW)
GPIO.output(pin_sda, GPIO.LOW)
GPIO.output(pin_rs , GPIO.LOW)
GPIO.output(pin_res, GPIO.HIGH)


#promenna pro ukladani aktualniho stavu displeje do pameti RasPi
mapa={}



# promenna pro font (je pouzity externi font 5x8 bodu. Mezi znaky se vklada mezera 1 bod)
# matice, do ktere je mozne zapsat znaky, ma tedy velikost 14x6 znaku
font={}

# nastaveni kontrastu displeje (vetsi cislo = tmavsi pozadi)
# Tuto hodnotu je treba vyzkouset. Kazdy displej ji muze mit trochu jinou. Take zalezi na okolni teplote.
# Kdyz bude displej moc tmavy, tak se musi nastavit trochu nizsi cislo, kdyz bude naopak moc svetly, tak se musi cislo zvetsit.
# Detaily jsou uvedene v katalogovem listu.
defaultni_kontrast = 60 

# tabulka pro prekodovani ceskych znaku z unicode na pozici v mensim fontu
cz={}
cz[345] = 253      # r s hackem
cz[237] = 161      # i s carkou
cz[353] = 231      # s s hackem
cz[382] = 167      # z s hackem
cz[357] = 156      # t s hackem
cz[269] = 159      # c s hackem
cz[253] = 236      # y s carkou
cz[367] = 133      # u s krouzkem
cz[328] = 229      # n s hackem
cz[250] = 163      # u s carkou
cz[283] = 216      # e s hackem
cz[271] = 212      # d s hackem
cz[225] = 160      # a s carkou
cz[233] = 130      # e s carkou
cz[243] = 162      # o s carkou

cz[344] = 252      # R s hackem
cz[205] = 214      # I s carkou
cz[352] = 230      # S s hackem
cz[381] = 166      # Z s hackem
cz[356] = 155      # T s hackem
cz[268] = 172      # C s hackem
cz[221] = 237      # Y s carkou
cz[366] = 222      # U s krouzkem
cz[327] = 213      # N s hackem
cz[218] = 233      # U s carkou
cz[282] = 183      # E s hackem
cz[270] = 210      # D s hackem
cz[193] = 181      # A s carkou
cz[201] = 144      # E s carkou
cz[211] = 224      # O s carkou

cz[228] = 158      # prehlasovane a
cz[235] = 159      # prehlasovane e
cz[239] = 160      # prehlasovane i
cz[246] = 161      # prehlasovane o
cz[252] = 162      # prehlasovane u
cz[196] = 163      # prehlasovane A
cz[214] = 164      # prehlasovane O
cz[220] = 165      # prehlasovane U

cz[176] = 166      # stupen
cz[177] = 241      # plus minus
cz[171] = 174      # dvojsipka vlevo
cz[166] = 173      # prerusene svislitko
cz[223] = 225      # beta





# ===========================  HLAVNI  PROGRAM ============================



def main():


  nacist_font("/home/pi/font.txt")   # nacteni obycejneho fontu 5x8 bodu
  ginit()          # inicializace displeje


  # textový rezim
  slovo("Ukázka práce" ,  6 , 0 , False)
  slovo("s  displejem" ,  6 , 1 , False)

  #vykresleni elipsy
  for kruh in range(0,6283,2):
    x = int(((math.sin(kruh/1000.0) * 30.0)) + 42)
    y = int(((math.cos(kruh/1000.0) *  5.0)) + 23)
    plot(x,y,1)


  # inverzni napis na predposlednim radku
  slovo("   Ukazatel   " ,  0 , 4 , True)

  
  # grafika  (ramecek na spodni radce)
  for i in range (84):
    mplot(i,47,1)
    mplot(i,40,1)
    mplot(i,31,1)

  for i in range (40,47,1):
    mplot(0,i,1)
    mplot(83,i,1)

  # uvodni vyplneni casti ramecku tmavym sloupcem
  for x in range(2,50,1):
    for y in range(42,46,1):
      mplot(x,y,1)
  mdump()


  # postupne zvežtsovani a zmensovani tmaveho sloupce
  for aktual in range(50,80):     # zvetsovani z 50 na 80
    for y in range(42,46,1):
      plot(aktual,y,1)
    time.sleep(0.02)   

  for aktual in range(80,10,-1):  # zmensovani z 80 na 10
    for y in range(42,46,1):
      plot(aktual,y,0)
    time.sleep(0.05)   

  for aktual in range(10,70):     # zvetsovani z 10 na 70
    for y in range(42,46,1):
      plot(aktual,y,1)
    time.sleep(0.03)   


  # jemne vodorovne posouvani inverzniho textu po cernem displeji
  disclear(0xFF)
  for i in range (0,30,1):
    slovo (" POSUN ", i , 3, True)
    time.sleep(0.1)
  for i in range (30,0,-1):
    slovo (" POSUN ", i , 3, True)
    time.sleep(0.1)


  disclear()

  # zobrazeni aktualniho datumu a casu na 10 sekund
  pocitadlo = 0
  while (pocitadlo < 10):
    akts = int(time.time())
    while (akts == int(time.time())):
      time.sleep(0.1)

    datum = time.strftime("%d.%m.", time.localtime())
    cas = time.strftime("%H:%M:%S", time.localtime())
  
    slovo("Datum:  " + datum , 0 , 0 , False)
    slovo("Čas:  " + cas     , 0 , 1 , False)
    pocitadlo = pocitadlo + 1





  # nahrani obrazku 84x48 bodu
  load_bmp8448("/home/pi/dis8448.bmp")
  time.sleep(5)

  # vyteckovani nahodnymi inverznimi body
  for i in range(3000):
    x= int(random.random() * 84)
    y= int(random.random() * 48)
    plot(x,y,2)
    

  slovo("       " , 21, 2, False)
  slovo(" KONEC " , 21, 3, False)
  slovo("       " , 21, 4, False)



# ----------------   ovladaci podprogramy pro displeje ----------------------



# posle 8 bitu postupne na datovy drat. Pri kazdem bitu udela impulz na hodinach
def sbajt(cislo):
  for i in range(8):                   # i = 0 az 7
    pozice = (0b10000000 >> i)         # pozice bitu se pri kazdem pruchodu smyckou posouva vpravo
    bit = ((cislo & pozice) >> (7-i))  # vypocet bitu "1" nebo "0"
    GPIO.output(pin_sda, bit)
    tik()


def tik():
  time.sleep(0.0000001)              # tiknuti hodinama do "1"
  GPIO.output(pin_scl, GPIO.HIGH)
  time.sleep(0.00000001)              # podle kat.listu musi pulz CLK-"H" trvat alespon 100ns
  GPIO.output(pin_scl, GPIO.LOW)     # a zpatky do "0"



# resetovaci impulz - resetuje se kratkou "0"
def reset():
  GPIO.output(pin_res, GPIO.LOW)
  time.sleep(0.000001)           
  GPIO.output(pin_res, GPIO.HIGH)
  time.sleep(0.001)   # chvili pockat, nez se displej vzpamatuje z resetu




# nastaveni RS bitu na "prikazy"
def prikazy():
  GPIO.output(pin_rs, GPIO.LOW)




# nastaveni RS bitu na "data"
def data():
  GPIO.output(pin_rs, GPIO.HIGH)




#zakladni nastaveni displeje do grafickeho rezimu
def ginit():
  reset()     # resetovaci impulz


  prikazy()
  sbajt(0b00100011)   # function set: aktivni chip, horizontalni adresovani, prepnuti na rozsirenou instrukcni sadu
  sbajt(0b00010100)   # bias system se nastavi na hodnotu 4
  sbajt(0b10000000 + defaultni_kontrast)  #  na Vop registru se nastavi kontrast
  sbajt(0b00100000)   # navrat na zakladni instrukcni sadu
  sbajt(0b00001100)   # display control: normalni rezim


  disclear()          # smazani celeho displeje (zaplneni displeje nulovymi daty)




#vyplneni celeho displeje jednim kodem (smazani displeje)
def disclear(pattern = 0):
  prikazy()
  sbajt(0b01000000) # Y=0
  sbajt(0b10000000) # X=0
  data()
  for adresa in range (504):        # zaplneni celeho displeje (504 bajtu) nejakym kodem 
    sbajt(pattern)
    mapa[adresa] = pattern

  prikazy() # posledni operaci pri mazani displeje musi byt NOP, jinak jsou problemy s poslednim bajtem
  sbajt(0)




#==========================================================================
# ======= operace s mensim fontem (byl pouzity na displeji 128x128 bodu)=======
# tento font ma definovane znaky pomoci 5 bajtu zleva do prava

# nacteni fontu ze souboru do seznamu "font[]"
def nacist_font(jmenosouboru):
  fontfile=file(jmenosouboru,"r")
  adresafontu=0
  for radka in fontfile:
    rozlozeno = radka.split(",")                   # vysosani jednotlivych bajtu z jedne radky ...
    for bajt in range(5):
      font[adresafontu] = int(rozlozeno[bajt][-4:],0) # ... a ulozeni kazdeho toho bajtu do seznamu
      adresafontu = adresafontu + 1
  fontfile.close()


# tisk 1 znaku z fontu
def znak(kod,x,y,inverze = False):  # x=0 az 83 (mikrosloupec) ,y= 0 az 5 (znakova radka), inverze textu

  adresa = (y * 84) + x    # adresa osmice bitu na displeji pri pouziti vertikalniho adresovani

  prikazy()             # nastaveni poze [X,Y]
  sbajt(0b10000000 + x) # X je v mikrosloupcich
  sbajt(0b01000000 + y) # Y zustava ve znakovych radkach


  data()
  kod = kod-32
  for adr in range(kod*5,(kod*5)+5):
    if (inverze == False):
      novydata = font[adr]
      sbajt(novydata)

    else:
      novydata = ~(font[adr])
      sbajt(novydata)


    mapa[adresa] = novydata
    adresa = adresa + 1 
  
  # za kazdym znakem se vytvori jeste jeden prazdny mikrosloupec jako mezera mezi znaky  
  if (inverze == False):
    sbajt(0b00000000) # mezera za poslednim pismenem
    mapa[adresa] = 0b00000000
  else:
    sbajt(0b11111111) # inverzni mezera za poslednim pismenem
    mapa[adresa] = 0b11111111

                  #  ????? 
  prikazy()       # za kazdym znakem je treba udelat NOP, jinak jsou problemy se zobrazenim posledniho mikrosloupce
  sbajt(0)







#tisk nekolika znaku za sebou (mensi font)
def slovo(text,x,y,inverze=False):  # x=0 az 83 (mikrosloupce); y (horni okraj znaku) = 0 az 5 

  if (x < 84 and x >= 0 and y < 6 and y >= 0):    # testovani na spravne zadanou polohu
    
    if (isinstance(text, unicode) == False):  # pokud text neni v unicode, tak ho preved
      text =  unicode(text, "utf-8")           # prevod textu z UTF-8 na unicode
  
    # cely text prochazet znak po znaku a tisknout
    for zn in range (len(text)):
      if (x < 79):  # kontrola prekroceni sirky displeje (sirka displeje - sirka znaku = 84 - 6 = 78)
        if (ord(text[zn:zn + 1]) > 127):   # pokud jde o neASCII znak, provest prekodovani podle tabulky
          try:                    # pokud neni specialni kod nadefinovany v tabulce, ...
            znak(cz[ord(text[zn:zn + 1])], x, y,inverze)
          except:                 # ... skoncil by program chybou neexistujiciho indexu promenne cz[].
            znak(177, x, y,inverze)  # V tom pripade se nahradi nedefinovany znak znakem "sachovnice"
  
        else:
          znak(ord(text[zn:zn + 1]), x, y,inverze) # ASCII znaky tisknout normalne
        x = x + 6




# zobrazeni(smazani/inverze) 1 bodu na souradnicich [x,y] v rozsahu [0 az 83, 0 az 47]
# styl: 0...smaz bod ; 1...zobraz bod, 2...invertuj bod
def plot(x,y,styl=1):

  if (x < 84 and x >= 0 and y < 48 and y >= 0):    # testovani na spravne zadanou polohu
 
    radka_y = y / 8    # zjisteni radky (0 az 5), na ktere se nachazi pozadovany bod
    y_bit = y % 8      # poloha bitu (0 az 7) v prislusne radce 
  
    adresa =  (radka_y * 84 )+ x   # adresa promenne mapa[], ktere se tyka upravovany bajt
    puvodni_data = mapa[adresa] # zjisteni aktualniho stavu zobrazeni prislusneho bajtu (zjistuje se z pameti RasPi)

    if (styl == 0):    # smazani bodu
      novy_data = (~(0b00000001 << y_bit) & puvodni_data)
    if (styl == 1):    # zobrazeni bodu
      novy_data = ((0b00000001 << y_bit) | puvodni_data)
    if (styl == 2):    # inverze bodu
      novy_data = ((0b00000001 << y_bit) ^ puvodni_data)
  
  
  
    # nastaveni polohy bajtu na displeji
    prikazy()
    sbajt(0b01000000 + radka_y)  # Y je ve znakovych radkach (0 az 5)
    sbajt(0b10000000 + x)        # X je v mikrosloupcich 0 az 83 (zustava stejne jako na vstupu funkce)
  
    # zapsani upraveneho bajtu do displeje
    data()
    sbajt(novy_data)
    
    mapa[adresa] = novy_data   # zapsani zmeneneho bajtu i do pameti RasPi

                    #  ????? 
    prikazy()       # za kazdym plotem je treba udelat NOP, jinak jsou problemy se zobrazenim posledniho bodu
    sbajt(0)


# stejna funkce jaklo plot(), akorat pracuje pouze s pameti, takze je treba na zaver pouzit funkci mdump()
def mplot(x,y,styl=1):

  if (x < 84 and x >= 0 and y < 48 and y >= 0):    # testovani na spravne zadanou polohu

    radka_y = y / 8    # zjisteni radky (0 az 5), na ktere se nachazi pozadovany bod
    y_bit = y % 8      # poloha bitu (0 az 7) v prislusne radce 
  
    adresa =  (radka_y * 84 )+ x   # adresa promenne mapa[], ktere se tyka upravovany bajt
  
    puvodni_data = mapa[adresa] # zjisteni aktualniho stavu zobrazeni prislusneho bajtu (zjistuje se z pameti RasPi)
    if (styl == 0):    # smazani bodu
      novy_data = (~(0b00000001 << y_bit) & puvodni_data)
    if (styl == 1):    # zobrazeni bodu
      novy_data = ((0b00000001 << y_bit) | puvodni_data)
    if (styl == 2):    # inverze bodu
      novy_data = ((0b00000001 << y_bit) ^ puvodni_data)
    
    mapa[adresa] = novy_data   # zapsani zmeneneho bajtu i do pameti RasPi
    




# rychle preneseni obsahu pameti (promenne mapa[]) do displeje:
def mdump():

  prikazy()
  sbajt(0b01000000) # Y=0    # nastaveni nulove pozice na displeji
  sbajt(0b10000000) # X=0

  data()
  for adresa in range (504): # zaplneni celeho displeje obsahem promenne mapa[]
    sbajt(mapa[adresa])

                  #  ????? 
  prikazy()       # Stejne jako u "disclear()" musi byt poslednim prikazem NOP, jinak se nezobrazi posledni bajt
  sbajt(0)
    


# nacteni dvoubarevneho BMP obrazku 84x48 bodu do promenne mapa[]
# POZOR: Bez jakychkoli testu na korektni format BMP souboru!
def load_bmp8448(jmeno_obrazku):
  soubor=open(jmeno_obrazku, "rb")  # nacteni obrazku do promenne data[]
  data = soubor.read()  
  soubor.close()                    # uzavreni souboru

  # podrobna specifikace hlavicky BMP souboru je tady:
  # http://www.root.cz/clanky/graficky-format-bmp-pouzivany-a-pritom-neoblibeny
  # zacatek obrazovych dat urcuji 4 bajty v souboru na pozicich 10 az 13 (desitkove) od zacatku souboru

  zacatekdat = ord(data[10]) + (ord(data[11]) * 256) + (ord(data[12]) * 65536) + (ord(data[13]) * 16777216)

  bajt=zacatekdat
  for supery in range (47,-1,-1):   # prepocet a bitove otoceni jednotlivych bajtu v obrazku  
    for xbajt in range (12):
      for b in range (8):
        superx = (xbajt * 8) + b
        maska = (1 << (7-b))   
        #kazdy bit z grafickych dat se na prislusne pozici na displeji (resp. v promenne mapa[]) bud rozsviti, nebo zhasne
        if (ord(data[bajt]) & maska != 0):   # dvoubarevna windowsovska bitmapa ma opacne nastavene bity nez displej:
          mplot(superx,supery,0)          # jednickove bity jsou bile body
        else:
          mplot(superx,supery,1)          # nulove bity jsou cerne body
      bajt = bajt + 1 

  mdump()    # na zaver se obrazek rychle prenese z promenne mapa[], na displej










if __name__ == '__main__':
  main()








