This project does not use the IoT module, however, it is a typical IoT, where the main actions are performed by the MikroTik router. Image formation is in demand, first of all, as part of IoT devices, the project allows you to expand the functionality of the router dedicated to collecting data from various sensors. In addition, this project will closely intersect with the following, directly using the IoT module. The whole project and its individual components are still in the development stage, but since this is a hobby project and development can go on forever, I want to share the main ideas.
The scope of application of E-Ink displays is very diverse, as an example, we can consider a hospital department: lists of patients and diagnoses at the entrance to the wards, a list of doctors on duty at the reception desk, today’s menu in the canteen - any information that requires regular updating is easily formed on the screen and does not require energy for further display.
This version is written for the GooDisplay 7.3-inch color ePaper module 800x480 GDEP073E01(E6) with ESP32 board, on its basis it is possible to organize work with any display, I invite those interested to join.
https://www.good-display.com/product/533.html
Look for the official shop on AliExpress
Program for ESP32 in Arduino IDE is based on the manufacturer’s example code
https://github.com/DenSyo77/GDEP073E01WebServer
Web server and API implemented here are the basis of the project, they need to be adapted to each specific display.
Main MikroTik script
# MikroTik E-Ink Display
#
# Drawing text on the screen is implemented based on the Adafruit GFX Library code
# https://github.com/adafruit/Adafruit-GFX-Library
:local einkAddress "IP address of display"
# Palette of the display {red;green;blue} use any previous color to create an unused color
:local einkPalette {{0;0;0};{0xff;0xff;0xff};{0xff;0xff;0};{0xff;0;0};{0;0;0};{0;0;0xff};{0;0xff;0}}
:local einkWidth 800
:local einkHeight 480
:local fetchSendSize 16000
:global einkRunCommands
:global einkFontName
:global einkFontSize
:global einkFont
:local einkRow ($einkWidth / 2)
:local einkBitmapSize ($einkRow * $einkHeight)
:local einkScrBuf [:toarray ""]
:local doFetch true
:local doConsolePut false
:local cmdNum 0
:while ($cmdNum < [:len $einkRunCommands]) do={
:local runCmd ($einkRunCommands->$cmdNum)
:if (($runCmd->"cmd") = "print") do={
:if (($runCmd->"font") != $einkFontName || ($runCmd->"size") != $einkFontSize) do={
:set einkFontName ($runCmd->"font")
:set einkFontSize ($runCmd->"size")
:if ([:pick $einkFontName 0 3] = "DOS") do={
:local importResult [import file=("einkFontDOS.rsc")]
} else={
:local importResult [import file=("einkFont".$einkFontName.".rsc")]
}
}
:local sx ($runCmd->"x")
:local sy ($runCmd->"y")
:local colorL (($runCmd->"color") & 0x0F)
:local colorH (($runCmd->"color") & 0xF0)
:local textLen 0
:local chars [:convert from=raw to=byte-array ($runCmd->"text")]
:for i from=0 to=([:len $chars] - 1) step=1 do={
:local charCode (($chars->$i) - ($einkFont->"first"))
:if ([:typeof ($einkFont->"glyph"->$charCode)] != "nothing") do={
:set textLen ($textLen + ($einkFont->"glyph"->$charCode->3))
}
}
:if (($runCmd->"align") = "center") do={
:set sx ($sx - $textLen / 2)
}
:if (($runCmd->"align") = "right") do={
:set sx ($sx - $textLen)
}
:for i from=0 to=([:len $chars] - 1) step=1 do={
:local charCode (($chars->$i) - ($einkFont->"first"))
:if ([:typeof ($einkFont->"glyph"->$charCode)] != "nothing") do={
:local bo ($einkFont->"glyph"->$charCode->0)
:local bit 0
:local bits 0
:for yy from=0 to=(($einkFont->"glyph"->$charCode->2) - 1) step=1 do={
:for xx from=0 to=(($einkFont->"glyph"->$charCode->1) - 1) step=1 do={
:if ($bit & 7 = 0) do={
:set bits ($einkFont->"bitmap"->$bo)
:set bo ($bo + 1)
}
:local x ($sx + ($einkFont->"glyph"->$charCode->4) + $xx)
:local y ($sy + ($einkFont->"glyph"->$charCode->5) + $yy)
:if ($x >= 0 && $x < $einkWidth && $y >= 0 && $y < $einkHeight) do={
:local idx ($y * $einkRow + $x / 2)
:local isLow ($x / 2 * 2 < $x)
:if ($bits & 0x80) do={
:if ($isLow) do={
:set ($einkScrBuf->$idx) ((($einkScrBuf->$idx) & 0xF0) + $colorL)
} else={
:set ($einkScrBuf->$idx) ((($einkScrBuf->$idx) & 0x0F) + $colorH)
}
}
}
:set bit ($bit + 1)
:set bits ($bits << 1)
}
}
:set sx ($sx + ($einkFont->"glyph"->$charCode->3))
}
}
}
:if (($runCmd->"cmd") = "bmp") do={
:if ([:len [/file/find where name=($runCmd->"file")]]) do={
:local fileSize [/file get ($runCmd->"file") size]
:if ($fileSize > $einkBitmapSize) do={
:local bmpFile [:convert from=raw to=byte-array ([/file/read file=($runCmd->"file") offset=0 chunk-size=118 as-value]->"data")]
:local bmpOffset (($bmpFile->10) + (($bmpFile->11) << 8) + (($bmpFile->12) << 16) + (($bmpFile->13) << 24))
:local bmpWidth (($bmpFile->18) + (($bmpFile->19) << 8) + (($bmpFile->20) << 16) + (($bmpFile->21) << 24))
:local bmpHeight (($bmpFile->22) + (($bmpFile->23) << 8) + (($bmpFile->24) << 16) + (($bmpFile->25) << 24))
:local bmpColorsBits (($bmpFile->28) + (($bmpFile->29) << 8))
:local bmpColorsCount (($bmpFile->46) + (($bmpFile->47) << 8) + (($bmpFile->48) << 16) + (($bmpFile->49) << 24))
:if (($bmpFile->0) = 66 && ($bmpFile->1) = 77 && $bmpWidth = $einkWidth && $bmpHeight = $einkHeight && $bmpColorsBits = 4 && ($fileSize - $bmpOffset) = $einkBitmapSize) do={
:local colorsPalette [:toarray ""]
:for i from=0 to=($bmpColorsCount - 1) step=1 do={
:local bmpCb ($bmpFile->(54 + $i * 4))
:local bmpCg ($bmpFile->(55 + $i * 4))
:local bmpCr ($bmpFile->(56 + $i * 4))
:local j 0
:local notfound true
:local nearDelta
:local nearColor
:while (($j < [:len $einkPalette]) && $notfound) do={
:local delta ((($einkPalette->$j->0) - $bmpCr) * (($einkPalette->$j->0) - $bmpCr) + (($einkPalette->$j->1) - $bmpCg) * (($einkPalette->$j->1) - $bmpCg) + (($einkPalette->$j->2) - $bmpCb) * (($einkPalette->$j->2) - $bmpCb))
:if ($j = 0 || $delta < $nearDelta) do={
:set nearDelta $delta
:set nearColor $j
}
:if ($delta = 0) do={
:set notfound false
} else={
:set j ($j + 1)
}
}
:set colorsPalette ($colorsPalette, $nearColor)
}
:for rowRead from=($einkHeight - 1) to=0 step=-1 do={
:local fileOffset ($rowRead * $einkRow + $bmpOffset)
:local bmpChunk [:convert from=raw to=byte-array ([/file/read file=($runCmd->"file") offset=$fileOffset chunk-size=$einkRow as-value]->"data")]
:for i from=0 to=($einkRow - 1) step=1 do={
:local bh (($bmpChunk->$i) >> 4)
:local bl (($bmpChunk->$i) & 15)
:set bh ($colorsPalette->$bh)
:set bl ($colorsPalette->$bl)
:set ($bmpChunk->$i) ($bl + ($bh << 4))
}
:set einkScrBuf ($einkScrBuf, $bmpChunk)
}
}
}
}
}
:if (($runCmd->"cmd") = "screen") do={
:for i from=1 to=375 step=1 do={
:set einkScrBuf ($einkScrBuf, ($runCmd->"color"))
}
:for i from=1 to=9 step=1 do={
:set einkScrBuf ($einkScrBuf, $einkScrBuf)
}
}
:if (($runCmd->"cmd") = "nofetch") do={
:set doFetch false
}
:if (($runCmd->"cmd") = "toconsole") do={
:set doConsolePut true
}
:set cmdNum ($cmdNum + 1)
}
:if ($doConsolePut) do={
:local i 0
:while ($i < $einkBitmapSize) do={
:local sendBuf [:toarray ""]
:for j from=$i to=($i + $fetchSendSize - 1) step=1 do={
:set sendBuf ($sendBuf, ($einkScrBuf->$j))
}
:put [:convert from=byte-array to=hex $sendBuf]
:set i ($i + $fetchSendSize)
}
:put [:len $einkScrBuf]
}
:if ($doFetch) do={
:local fetchResult ([/tool fetch mode=http http-method="post" idle-timeout=60s output=none url=("http://".$einkAddress."/draw") as-value]->"status")
:local i 0
:while ($i < $einkBitmapSize && $fetchResult = "finished") do={
:local sendBuf [:toarray ""]
:for j from=$i to=($i + $fetchSendSize - 1) step=1 do={
:set sendBuf ($sendBuf, ($einkScrBuf->$j))
}
:set fetchResult ([/tool fetch mode=http http-method="post" idle-timeout=120s output=none url=("http://".$einkAddress."/draw") http-data=[:convert from=byte-array to=hex $sendBuf] as-value]->"status")
:set i ($i + $fetchSendSize)
}
}
Usage example (this script needs to be run on a schedule)
# Open-meteo - Free Weather API
# https://open-meteo.com
# Go https://open-meteo.com/en/docs page, click "Search" button in "Location and Time" section and enter Your location name to get coordinates, type Latitude and Longitude in fetch url
:local getOpenMeteo [/tool fetch mode=https http-method="get" idle-timeout=30s output=user url="https://api.open-meteo.com/v1/forecast?latitude=43.1056&longitude=131.8735&hourly=temperature_2m¤t=temperature_2m,relative_humidity_2m,apparent_temperature,is_day,precipitation,rain,showers,snowfall,weather_code,cloud_cover,pressure_msl,surface_pressure,wind_speed_10m,wind_direction_10m,wind_gusts_10m&timezone=auto&forecast_days=2" as-value]
:local currTemperature
:if (($getOpenMeteo->"status") = "finished") do={
:local openMeteo [:deserialize from=json value=($getOpenMeteo->"data")]
:local decimalPoint [:find ($openMeteo->"current"->"temperature_2m") "."]
:local intPart
:if ([:typeof $decimalPoint] != nil) do={
:set intPart [:tonum [:pick ($openMeteo->"current"->"temperature_2m") 0 $decimalPoint]]
:local decimalPart [:tonum [:pick ($openMeteo->"current"->"temperature_2m") ($decimalPoint + 1)]]
:if ($decimalPart > 4) do={
:if ($intPart < 0) do={
:set intPart ($intPart - 1)
} else={
:set intPart ($intPart + 1)
}
}
} else={
:set intPart [:tonum ($openMeteo->"current"->"temperature_2m")]
}
:set currTemperature [:tostr $intPart]
:if ($intPart > 0) do={
:set currTemperature ("+".$currTemperature)
}
:set currTemperature ($currTemperature.($openMeteo->"current_units"->"temperature_2m"))
}
# Rextended date format function
# http://forum.mikrotik.com/t/changing-the-mmm-dd-yyyy-date-format/5183/10
:local currdatetimestr do={
/system clock
:local vdate [get date]
:local vtime [get time]
:local vgmt [:tonum [get gmt-offset]]; :if ($vgmt > 0x7FFFFFFF) do={:set vgmt ($vgmt - 0x100000000)}
:local prMntDays [:toarray "0,0,31,59,90,120,151,181,212,243,273,304,334"]
:local daysOnMnt [:toarray "0,31,28,31,30,31,30,31,31,30,31,30,31"]
:local LcaseMnts [:toarray "0,jan,feb,mar,apr,may,jun,jul,aug,sep,oct,nov,dec"]
:local PcaseMnts [:toarray "0,Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec"]
:local UcaseMnts [:toarray "0,JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC"]
:local LcaseWeekDays [:toarray "thu,fri,sat,sun,mon,tue,wed"]
:local PcaseWeekDays [:toarray "Thu,Fri,Sat,Sun,Mon,Tue,Wed"]
:local UcaseWeekDays [:toarray "THU,FRI,SAT,SUN,MON,TUE,WED"]
:local NumbrWeekDays [:toarray "4,5,6,7,1,2,3"]
:local Fzerofill do={:return [:pick (100 + $1) 1 3]}
:local gmtSg "+"; :if ($vgmt < 0) do={:set gmtSg "-"; :set vgmt ($vgmt * -1)}
:local gmtHr [:pick [:totime $vgmt] 0 2]
:local gmtMn [:pick [:totime $vgmt] 3 5]
:local vdoff [:toarray "0,4,5,7,8,10"]
:local MM [:pick $vdate ($vdoff->2) ($vdoff->3)]
:local M [:tonum $MM]
:if ($vdate ~ ".../../....") do={
:set vdoff [:toarray "7,11,1,3,4,6"]
:set M ([:find "xxanebarprayunulugepctovecANEBARPRAYUNULUGEPCTOVEC" [:pick $vdate ($vdoff->2) ($vdoff->3)] -1] / 2)
:if ($M>12) do={:set M ($M - 12)}
:set MM [:pick (100 + $M) 1 3]
}
:local yyyy [:pick $vdate ($vdoff->0) ($vdoff->1)]
:local Leap "No-Leap"
:if ((($yyyy - 1968) % 4) = 0) do={:set Leap "Leap"; :set ($prMntDays->1) -1; :set ($prMntDays->2) 30; :set ($daysOnMnt->2) 29}
:local mmm ($LcaseMnts->$M)
:local Mmm ($PcaseMnts->$M)
:local MMM ($UcaseMnts->$M)
:local MD ($daysOnMnt->$M)
:local dd [:pick $vdate ($vdoff->4) ($vdoff->5)]
:local d [:tonum $dd] ; :local totd ((($yyyy - 1970) * 365) + (($yyyy - 1968) / 4) + ($prMntDays->$M) + ($d - 1))
:local YD (($prMntDays->$M) + $d)
:local www ($LcaseWeekDays->($totd % 7))
:local Www ($PcaseWeekDays->($totd % 7))
:local WWW ($UcaseWeekDays->($totd % 7))
:local WD ($NumbrWeekDays->($totd % 7))
:local HH [:pick $vtime 0 2]
:local H [:tonum $HH]
:local hh ([:tonum $HH] % 12); :if ($hh = 0) do={:set hh 12}; :set hh [$Fzerofill $hh]
:local h [:tonum $hh]
:local a "A"; :if ([:tonum $HH] > 11) do={:set a "P"}
:local aa "$a\4D"
:local mm [:pick $vtime 3 5]
:local m [:tonum $mm]
:local ss [:pick $vtime 6 8]
:local s [:tonum $ss]
:local Z "$gmtSg$gmtHr:$gmtMn"
:local Unix (((((($totd * 24) + $H) * 60) + $m) * 60) + $s - $vgmt)
:return {
"utc"="$yyyy-$MM-$dd";
"UTC"="$yyyy-$MM-$dd\54$HH:$mm:$ss$Z";
"Z"=$Z;
"d"=$d;
"dd"=$dd;
"M"=$M;
"MM"=$MM;
"mmm"=$mmm;
"yyyy"=$yyyy;
"www"=$www;
"YD"=$YD;
"MD"=$MD;
"WD"=$WD;
"Leap"=$Leap;
"Unix"=$Unix
}
}
:local currDate [$currdatetimestr]
:local colorDay 0x11
:local colorDay2 0
:if (($currDate->"WD") = 6 || ($currDate->"WD") = 7) do={
:set colorDay 0x22
:set colorDay2 0x33
}
:local infoRos [/system resource print as-value]
:set ($infoRos->"ver") [:pick ($infoRos->"version") 0 [:find ($infoRos->"version") " "]]
:local einkDisplayPatterns {
{
{ "cmd"="bmp"; "file"="einkSyoFamilyFC2006.bmp" };
{ "cmd"="print"; "text"=($currDate->"mmm"); "font"="ManropeBold"; "size"=24; "x"=90; "y"=40; "align"="center"; "color"=$colorDay };
{ "cmd"="print"; "text"=($currDate->"www"); "font"="ManropeBold"; "size"=24; "x"=90; "y"=190; "align"="center"; "color"=$colorDay };
{ "cmd"="print"; "text"=($currDate->"d"); "font"="ManropeBold"; "size"=64; "x"=90; "y"=150; "align"="center"; "color"=$colorDay };
{ "cmd"="print"; "text"=$currTemperature; "font"="ManropeBold"; "size"=36; "x"=790; "y"=70; "align"="right"; "color"=0 }
};
{
{ "cmd"="bmp"; "file"="einkPlushaOnRouter.bmp" };
{ "cmd"="print"; "text"=($currDate->"mmm"); "font"="ManropeBold"; "size"=24; "x"=90; "y"=40; "align"="center"; "color"=$colorDay2 };
{ "cmd"="print"; "text"=($currDate->"www"); "font"="ManropeBold"; "size"=24; "x"=90; "y"=190; "align"="center"; "color"=$colorDay2 };
{ "cmd"="print"; "text"=($currDate->"d"); "font"="ManropeBold"; "size"=64; "x"=90; "y"=150; "align"="center"; "color"=$colorDay2 };
{ "cmd"="print"; "text"=$currTemperature; "font"="ManropeBold"; "size"=36; "x"=790; "y"=70; "align"="right"; "color"=0 }
};
{
{ "cmd"="bmp"; "file"="einkMikroTikBlack.bmp" };
{ "cmd"="print"; "text"="RouterOS is the operating system of RouterBOARD"; "font"="ManropeRegular"; "size"=14; "x"=400; "y"=380; "align"="center"; "color"=0x11 };
{ "cmd"="print"; "text"="and x86 platforms"; "font"="ManropeRegular"; "size"=14; "x"=400; "y"=430; "align"="center"; "color"=0x11 };
{ "cmd"="print"; "text"="it's time to play with"; "font"="ManropeRegular"; "size"=24; "x"=750; "y"=120; "align"="right"; "color"=0x11 }
};
{
{ "cmd"="screen"; "color"=0 };
{ "cmd"="print"; "text"="MMM MMM KKK TTTTTTTTTTT KKK"; "font"="DOSsymon"; "x"=16; "y"=64; "color"=0x11 };
{ "cmd"="print"; "text"="MMMM MMMM KKK TTTTTTTTTTT KKK"; "font"="DOSsymon"; "x"=16; "y"=80; "color"=0x11 };
{ "cmd"="print"; "text"="MMM MMMM MMM III KKK KKK RRRRRR OOOOOO TTT III KKK KKK"; "font"="DOSsymon"; "x"=16; "y"=96; "color"=0x11 };
{ "cmd"="print"; "text"="MMM MM MMM III KKKKK RRR RRR OOO OOO TTT III KKKKK"; "font"="DOSsymon"; "x"=16; "y"=112; "color"=0x11 };
{ "cmd"="print"; "text"="MMM MMM III KKK KKK RRRRRR OOO OOO TTT III KKK KKK"; "font"="DOSsymon"; "x"=16; "y"=128; "color"=0x11 };
{ "cmd"="print"; "text"="MMM MMM III KKK KKK RRR RRR OOOOOO TTT III KKK KKK"; "font"="DOSsymon"; "x"=16; "y"=144; "color"=0x11 };
{ "cmd"="print"; "text"="MikroTik RouterOS ".($infoRos->"ver")." (c) 1999-".($currDate->"yyyy"); "font"="DOSsymon"; "x"=16; "y"=176; "color"=0x11 };
{ "cmd"="print"; "text"="https://www.mikrotik.com/"; "font"="DOSsymon"; "x"=368; "y"=176; "color"=0x11 }
};
{
{ "cmd"="bmp"; "file"="einkLennaTest.bmp" };
{ "cmd"="print"; "text"="Hi guys!"; "font"="JetBrainsMonoBold"; "size"=36; "x"=30; "y"=400; "color"=0x11 }
}
}
:global einkRunCount
:if ([:typeof $einkRunCount] = "nothing" || $einkRunCount >= [:len $einkDisplayPatterns]) do={
:set einkRunCount 0
}
:global einkRunCommands ($einkDisplayPatterns->$einkRunCount)
/system script run einkDisplay
:set einkRunCount ($einkRunCount + 1)
Result on the screen
Script allows to display a sixteen-color BMP file of the corresponding resolution in any palette (the closest color will be selected in accordance with the specified palette for the screen) and print text in any font. The principle of working with fonts is taken from the Arduino library Adafruit GFX, to create a font you can use this tool:
https://rop.nl/truetype2gfx/
Example font
# MikroTik version Adafruit GFX font
# font: keyrus
# size (px): 8x16
:global einkFontName
:global einkFont
:if ($einkFontName = "DOSkeyrus") do={
:set einkFont { "type"="agfx"; "first"=32; "last"=126; "yadvance"=16;
"bitmap"={
0x6F; 0xFF; 0x66; 0x60; 0x66;
0xCF; 0x3C; 0xD2;
0x6C; 0xDB; 0xFB; 0x66; 0xCD; 0xBF; 0xB6; 0x6C;
0x18; 0x31; 0xF6; 0x3C; 0x38; 0x1F; 0x03; 0x87; 0x8D; 0xF0; 0xC1; 0x80;
0xC3; 0x8C; 0x30; 0xC3; 0x0C; 0x31; 0xC3;
0x38; 0xD9; 0xB1; 0xC7; 0x7B; 0xB3; 0x66; 0xCC; 0xEC;
0x6D; 0xE0;
0x36; 0xCC; 0xCC; 0xCC; 0x63;
0xC6; 0x33; 0x33; 0x33; 0x6C;
0x66; 0x3C; 0xFF; 0x3C; 0x66;
0x30; 0xCF; 0xCC; 0x30;
0x6D; 0xE0;
0xFE;
0xF0;
0x02; 0x0C; 0x30; 0xC3; 0x0C; 0x30; 0x40;
0x7D; 0x8F; 0x1E; 0x7D; 0x7A; 0xF9; 0xE3; 0xC6; 0xF8;
0x31; 0xCF; 0x0C; 0x30; 0xC3; 0x0C; 0x33; 0xF0;
0x7D; 0x8C; 0x18; 0x61; 0x86; 0x18; 0x60; 0xC7; 0xFC;
0x7D; 0x8C; 0x18; 0x33; 0xC0; 0xC1; 0x83; 0xC6; 0xF8;
0x0C; 0x38; 0xF3; 0x6C; 0xDF; 0xC3; 0x06; 0x0C; 0x3C;
0xFF; 0x83; 0x06; 0x0F; 0xC1; 0xC1; 0x83; 0xC6; 0xF8;
0x38; 0xC3; 0x06; 0x0F; 0xD8; 0xF1; 0xE3; 0xC6; 0xF8;
0xFF; 0x8C; 0x18; 0x30; 0xC3; 0x0C; 0x18; 0x30; 0x60;
0x7D; 0x8F; 0x1E; 0x37; 0xD8; 0xF1; 0xE3; 0xC6; 0xF8;
0x7D; 0x8F; 0x1E; 0x37; 0xE0; 0xC1; 0x83; 0x0C; 0xF0;
0xF0; 0x3C;
0x6C; 0x00; 0xDE;
0x0C; 0x63; 0x18; 0xC1; 0x83; 0x06; 0x0C;
0xFE; 0x00; 0x07; 0xF0;
0xC1; 0x83; 0x06; 0x0C; 0x63; 0x18; 0xC0;
0x7D; 0x8F; 0x18; 0x61; 0x83; 0x06; 0x00; 0x18; 0x30;
0x7D; 0x8F; 0x1E; 0xFD; 0xFB; 0xF7; 0x60; 0x7C;
0x10; 0x71; 0xB6; 0x3C; 0x7F; 0xF1; 0xE3; 0xC7; 0x8C;
0xFC; 0xCD; 0x9B; 0x37; 0xCC; 0xD9; 0xB3; 0x67; 0xF8;
0x3C; 0xCF; 0x0E; 0x0C; 0x18; 0x30; 0x61; 0x66; 0x78;
0xF8; 0xD9; 0x9B; 0x36; 0x6C; 0xD9; 0xB3; 0x6D; 0xF0;
0xFE; 0xCD; 0x8B; 0x47; 0x8D; 0x18; 0x31; 0x67; 0xFC;
0xFE; 0xCD; 0x8B; 0x47; 0x8D; 0x18; 0x30; 0x61; 0xE0;
0x3C; 0xCF; 0x0E; 0x0C; 0x1B; 0xF1; 0xE3; 0x66; 0x74;
0xC7; 0x8F; 0x1E; 0x3F; 0xF8; 0xF1; 0xE3; 0xC7; 0x8C;
0xF6; 0x66; 0x66; 0x66; 0x6F;
0x1E; 0x18; 0x30; 0x60; 0xC1; 0xB3; 0x66; 0xCC; 0xF0;
0xE6; 0xCD; 0xB3; 0x67; 0x8F; 0x1B; 0x33; 0x67; 0xCC;
0xF0; 0xC1; 0x83; 0x06; 0x0C; 0x18; 0x31; 0x67; 0xFC;
0xC7; 0xDF; 0xFF; 0xFD; 0x78; 0xF1; 0xE3; 0xC7; 0x8C;
0xC7; 0xCF; 0xDF; 0xFD; 0xF9; 0xF1; 0xE3; 0xC7; 0x8C;
0x38; 0xDB; 0x1E; 0x3C; 0x78; 0xF1; 0xE3; 0x6C; 0x70;
0xFC; 0xCD; 0x9B; 0x37; 0xCC; 0x18; 0x30; 0x61; 0xE0;
0x7D; 0x8F; 0x1E; 0x3C; 0x78; 0xF1; 0xEB; 0xDE; 0xF8; 0x30; 0x70;
0xFC; 0xCD; 0x9B; 0x37; 0xCD; 0x99; 0xB3; 0x67; 0xCC;
0x7D; 0x8F; 0x1B; 0x03; 0x81; 0x81; 0xE3; 0xC6; 0xF8;
0xFF; 0xFB; 0x4C; 0x30; 0xC3; 0x0C; 0x31; 0xE0;
0xC7; 0x8F; 0x1E; 0x3C; 0x78; 0xF1; 0xE3; 0xC6; 0xF8;
0xC7; 0x8F; 0x1E; 0x3C; 0x78; 0xF1; 0xB6; 0x38; 0x20;
0xC7; 0x8F; 0x1E; 0x3C; 0x7A; 0xF5; 0xFF; 0x6C; 0xD8;
0xC7; 0x8D; 0xB3; 0x63; 0x87; 0x1B; 0x36; 0xC7; 0x8C;
0xCF; 0x3C; 0xF3; 0x78; 0xC3; 0x0C; 0x31; 0xE0;
0xFF; 0x8E; 0x18; 0x61; 0x86; 0x18; 0x61; 0xC7; 0xFC;
0xFC; 0xCC; 0xCC; 0xCC; 0xCF;
0x81; 0x83; 0x83; 0x83; 0x83; 0x83; 0x83; 0x02;
0xF3; 0x33; 0x33; 0x33; 0x3F;
0x10; 0x71; 0xB6; 0x30;
0xFF;
0xD9; 0x80;
0x78; 0x19; 0xF6; 0x6C; 0xD9; 0x9D; 0x80;
0xE0; 0xC1; 0x83; 0xC6; 0xCC; 0xD9; 0xB3; 0x67; 0xB8;
0x7D; 0x8F; 0x06; 0x0C; 0x18; 0xDF; 0x00;
0x1C; 0x18; 0x31; 0xE6; 0xD9; 0xB3; 0x66; 0xCC; 0xEC;
0x7D; 0x8F; 0xFE; 0x0C; 0x18; 0xDF; 0x00;
0x39; 0xB6; 0x58; 0xF1; 0x86; 0x18; 0x63; 0xC0;
0x77; 0x9B; 0x36; 0x6C; 0xD9; 0x9F; 0x06; 0xCC; 0xF0;
0xE0; 0xC1; 0x83; 0x67; 0x6C; 0xD9; 0xB3; 0x67; 0xCC;
0x66; 0x0E; 0x66; 0x66; 0x6F;
0x0C; 0x30; 0x07; 0x0C; 0x30; 0xC3; 0x0C; 0x3C; 0xF3; 0x78;
0xE0; 0xC1; 0x83; 0x36; 0xCF; 0x1E; 0x36; 0x67; 0xCC;
0xE6; 0x66; 0x66; 0x66; 0x6F;
0xED; 0xFF; 0x5E; 0xBD; 0x7A; 0xF5; 0x80;
0xDC; 0xCD; 0x9B; 0x36; 0x6C; 0xD9; 0x80;
0x7D; 0x8F; 0x1E; 0x3C; 0x78; 0xDF; 0x00;
0xDC; 0xCD; 0x9B; 0x36; 0x6C; 0xDF; 0x30; 0x61; 0xE0;
0x77; 0x9B; 0x36; 0x6C; 0xD9; 0x9F; 0x06; 0x0C; 0x3C;
0xDC; 0xED; 0x8B; 0x06; 0x0C; 0x3C; 0x00;
0x7D; 0x8D; 0x81; 0xC0; 0xD8; 0xDF; 0x00;
0x10; 0x60; 0xC7; 0xE3; 0x06; 0x0C; 0x18; 0x36; 0x38;
0xCD; 0x9B; 0x36; 0x6C; 0xD9; 0x9D; 0x80;
0xCF; 0x3C; 0xF3; 0xCD; 0xE3; 0x00;
0xC7; 0x8F; 0x1E; 0xBD; 0x7F; 0xDB; 0x00;
0xC6; 0xD8; 0xE1; 0xC3; 0x8D; 0xB1; 0x80;
0xC7; 0x8F; 0x1E; 0x3C; 0x78; 0xDF; 0x83; 0x0D; 0xF0;
0xFF; 0x98; 0x61; 0x86; 0x18; 0xFF; 0x80;
0x1C; 0xC3; 0x0C; 0xE0; 0xC3; 0x0C; 0x30; 0x70;
0xFF; 0x3F; 0xF0;
0xE0; 0xC3; 0x0C; 0x1C; 0xC3; 0x0C; 0x33; 0x80;
0x77; 0xB8 };
"glyph"={
{ 0; 0; 0; 8; 0; 0 };
{ 0; 4; 10; 8; 2; -13 };
{ 5; 6; 4; 8; 1; -14 };
{ 8; 7; 9; 8; 0; -12 };
{ 16; 7; 13; 8; 0; -15 };
{ 28; 7; 8; 8; 0; -11 };
{ 35; 7; 10; 8; 0; -13 };
{ 44; 3; 4; 8; 1; -14 };
{ 46; 4; 10; 8; 2; -13 };
{ 51; 4; 10; 8; 2; -13 };
{ 56; 8; 5; 8; 0; -10 };
{ 61; 6; 5; 8; 1; -10 };
{ 65; 3; 4; 8; 2; -6 };
{ 67; 7; 1; 8; 0; -8 };
{ 68; 2; 2; 8; 3; -5 };
{ 69; 7; 8; 8; 0; -11 };
{ 76; 7; 10; 8; 0; -13 };
{ 85; 6; 10; 8; 1; -13 };
{ 93; 7; 10; 8; 0; -13 };
{ 102; 7; 10; 8; 0; -13 };
{ 111; 7; 10; 8; 0; -13 };
{ 120; 7; 10; 8; 0; -13 };
{ 129; 7; 10; 8; 0; -13 };
{ 138; 7; 10; 8; 0; -13 };
{ 147; 7; 10; 8; 0; -13 };
{ 156; 7; 10; 8; 0; -13 };
{ 165; 2; 7; 8; 3; -11 };
{ 167; 3; 8; 8; 2; -11 };
{ 170; 6; 9; 8; 1; -12 };
{ 177; 7; 4; 8; 0; -9 };
{ 181; 6; 9; 8; 1; -12 };
{ 188; 7; 10; 8; 0; -13 };
{ 197; 7; 9; 8; 0; -12 };
{ 205; 7; 10; 8; 0; -13 };
{ 214; 7; 10; 8; 0; -13 };
{ 223; 7; 10; 8; 0; -13 };
{ 232; 7; 10; 8; 0; -13 };
{ 241; 7; 10; 8; 0; -13 };
{ 250; 7; 10; 8; 0; -13 };
{ 259; 7; 10; 8; 0; -13 };
{ 268; 7; 10; 8; 0; -13 };
{ 277; 4; 10; 8; 2; -13 };
{ 282; 7; 10; 8; 0; -13 };
{ 291; 7; 10; 8; 0; -13 };
{ 300; 7; 10; 8; 0; -13 };
{ 309; 7; 10; 8; 0; -13 };
{ 318; 7; 10; 8; 0; -13 };
{ 327; 7; 10; 8; 0; -13 };
{ 336; 7; 10; 8; 0; -13 };
{ 345; 7; 12; 8; 0; -13 };
{ 356; 7; 10; 8; 0; -13 };
{ 365; 7; 10; 8; 0; -13 };
{ 374; 6; 10; 8; 1; -13 };
{ 382; 7; 10; 8; 0; -13 };
{ 391; 7; 10; 8; 0; -13 };
{ 400; 7; 10; 8; 0; -13 };
{ 409; 7; 10; 8; 0; -13 };
{ 418; 6; 10; 8; 1; -13 };
{ 426; 7; 10; 8; 0; -13 };
{ 435; 4; 10; 8; 2; -13 };
{ 440; 7; 9; 8; 0; -12 };
{ 448; 4; 10; 8; 2; -13 };
{ 453; 7; 4; 8; 0; -15 };
{ 457; 8; 1; 8; 0; -2 };
{ 458; 3; 3; 8; 2; -15 };
{ 460; 7; 7; 8; 0; -10 };
{ 467; 7; 10; 8; 0; -13 };
{ 476; 7; 7; 8; 0; -10 };
{ 483; 7; 10; 8; 0; -13 };
{ 492; 7; 7; 8; 0; -10 };
{ 499; 6; 10; 8; 0; -13 };
{ 507; 7; 10; 8; 0; -10 };
{ 516; 7; 10; 8; 0; -13 };
{ 525; 4; 10; 8; 2; -13 };
{ 530; 6; 13; 8; 1; -13 };
{ 540; 7; 10; 8; 0; -13 };
{ 549; 4; 10; 8; 2; -13 };
{ 554; 7; 7; 8; 0; -10 };
{ 561; 7; 7; 8; 0; -10 };
{ 568; 7; 7; 8; 0; -10 };
{ 575; 7; 10; 8; 0; -10 };
{ 584; 7; 10; 8; 0; -10 };
{ 593; 7; 7; 8; 0; -10 };
{ 600; 7; 7; 8; 0; -10 };
{ 607; 7; 10; 8; 0; -13 };
{ 616; 7; 7; 8; 0; -10 };
{ 623; 6; 7; 8; 1; -10 };
{ 629; 7; 7; 8; 0; -10 };
{ 636; 7; 7; 8; 0; -10 };
{ 643; 7; 10; 8; 0; -10 };
{ 652; 7; 7; 8; 0; -10 };
{ 659; 6; 10; 8; 1; -13 };
{ 667; 2; 10; 8; 3; -13 };
{ 670; 6; 10; 8; 1; -13 };
{ 678; 7; 2; 8; 0; -13 } }
}
}
You can also use this tool to create and edit fonts (Windows only):
https://forum.arduino.cc/t/excel-fonts-editor-converter-for-adafruit-utft-squix-ili9341_t3-oled_i2c/430653
This tool will allow you to create any symbols in the font and will help you get an idea of working with fonts in the Arduino environment. Don’t forget, the maximum character size in Arduino fonts is 255x255 pixels.
Archive with project, RSC files with fonts
https://syo.su/download/MikroTikEinkDisplay.zip
Read to get acquainted with the topic
https://learn.adafruit.com/preparing-graphics-for-e-ink-displays?view=all
Dithering tool for create image (page from web server on ESP32). Pure JavaScript, everything works locally in browser. Code is hard to read, it is compressed to fit on microcontroller.
https://syo.su/eink