{-# LANGUAGE
ViewPatterns
, NamedFieldPuns
, DisambiguateRecordFields #-}moduleHardware.LCD.CFA635(-- * LCD devicesLCD,withLCD,open,close-- * Display parameters,contrast,backlight,cursorStyle-- * Displaying text,clear,Pos(..),move,write-- * Indicator LEDs,setLED,clearLEDs)whereimportData.WordimportData.ListimportData.BitsimportControl.MonadimportControl.Monad.MaybeimportqualifiedSystem.Hardware.SerialportasSerimportqualifiedData.Digest.Table.CRC16asCRCimportqualifiedControl.ExceptionasExnclamp::(Orda)=>String->a->a->a->aclampfunlbubx|(x>=lb)&&(x<=ub)=x|otherwise=error("CFA635."++fun++": argument out of bounds")-- | Represents an open LCD device. Abstract.dataLCD=LCD{lcdPort::Ser.SerialPort,lcdWidth::Int,lcdHeight::Int}dataPos=PosIntIntderiving(Eq,Show)pClamp::String->LCD->Pos->PospClampfunLCD{lcdWidth,lcdHeight}(Pos(clampfun0(lcdWidth-1)->col)(clampfun0(lcdHeight-1)->row))=Poscolrowi8::Int->Word8i8=fromIntegralportSettings::Ser.SerialPortSettingsportSettings=Ser.SerialPortSettings{baudRate=Ser.B115200,bitsPerWord=8,stopb=Ser.One,parity=Ser.NoParity,flowControl=Ser.NoFlowControl,timeout=1}-- in sec / 10-- | Open the LCD at a particular device file.-- Maybe use @'withLCD'@ instead?open::FilePath->IOLCDopenportName=dop<-Ser.openSerialportNameportSettingsletx=LCDp204-- hard-coded size of the CFA635clearxclearLEDsxreturnx-- | Close the LCD file. Maybe use @'withLCD'@ instead?close::LCD->IO()closeLCD{lcdPort}=Ser.closeSeriallcdPort-- | Run an @'IO'@ action with a connection to an LCD.withLCD::FilePath->(LCD->IOa)->IOawithLCDf=Exn.bracket(openf)closedataPkt=PktWord8[Word8]deriving(Show)-- | Clear the screen.clear::LCD->IO()clearp=sendPktp$Pkt0x06[]-- | Set contrast. 0 is light, 254 is dark, 95 is recommended.contrast::LCD->Int->IO()contrastp(clamp"contrast"0254->x)=sendPktp$Pkt0x0D[i8x]-- | Set backlight brightness. 0 is off, 100 is full on.backlight::LCD->Int->IO()backlightp(clamp"backlight"0100->x)=sendPktp$Pkt0x0E[i8x]-- | Turn on or off an indicator LED. Valid indices are from @[0..7]@.setLED::LCD->Int->Bool->IO()setLEDp(clamp"setLED"07->i)st=sendPktp$Pkt0x22[i8(5+i),ifstthen100else0]-- | Turn off all indicator LEDs.clearLEDs::LCD->IO()clearLEDsp=forM_[0..7]$\i->setLEDpiFalse-- | Set the cursor style. Valid styles are from @[0..4]@.cursorStyle::LCD->Int->IO()cursorStylep(clamp"cursorStyle"04->i)=sendPktp$Pkt0x0C[i8i]encode::String->[Word8]encode=mapecwhereecx=casefromEnumxofi|i>127->187-- 'dot' character for unencodable|otherwise->fromIntegrali-- | Write to the LCD at a specified position.write::LCD->Pos->String->IO()writep@LCD{lcdWidth}(pClamp"write"p->Poscolrow)xs=sendPktp$Pkt0x1F(i8col:i8row:encodepxs)wherepxs=take(lcdWidth-col)xs-- | Move the cursor.move::LCD->Pos->IO()movep(pClamp"write"p->Poscolrow)=sendPktp$Pkt0x0B[i8col,i8row]sendI::(Integrala)=>Ser.SerialPort->a->IO()sendIp=Ser.sendCharp.toEnum.fromIntegralsend8::Ser.SerialPort->Word8->IO()send8=sendIsend16::Ser.SerialPort->Word16->IO()send16pn=dosendIp(n.&.0xFF)sendIp(n`shiftR`8)sendPkt1::LCD->Pkt->IO()sendPkt1LCD{lcdPort=p}(Pkttydat)=dosend8ptyletlenb=genericLengthdatsend8plenbmapM_(send8p)datsend16p$CRC.crc16(ty:lenb:dat)sendPkt::LCD->Pkt->IO()sendPktlp=dosendPkt1lp_<-recvPktlreturn()recv8::Ser.SerialPort->IO(MaybeWord8)recv8=fmap(fmap(fromIntegral.fromEnum)).Ser.recvChartrecv16::Ser.SerialPort->MaybeTIOWord16trecv16p=dol<-trecv8ph<-trecv8preturn(fromIntegrall.|.(fromIntegralh`shiftL`8))trecv8::Ser.SerialPort->MaybeTIOWord8trecv8=MaybeT.recv8recvPkt::LCD->IO(MaybePkt)recvPktLCD{lcdPort=p}=runMaybeT$dotyp<-trecv8plen<-trecv8pdat<-replicateM(fromIntegrallen)$trecv8pcs<-trecv16pwhen(CRC.crc16(typ:len:dat)/=cs)$fail"bad crc"return(Pkttypdat)