AES Algoritması Nasıl Çalışıyor ? Birlikte Gerçekleyelim – 1


Tarihçe

Matematik temelli yaklaşımlara sahip bir protokol, görece modern teknolojiler veyahut bir cihazın içerisinde koşan yazılım modüllerine bakıldığında ilgili yaklaşımın yıllar öncesinde bir standarta bindirildiğini görmek çokta zor olmasa gerek. AES’e gebe kalınmadan önce DES(Data Encryption Standart) kullanılmaktaymış. Ancak bu standart numerik olarak düşük şifreleme anahtar boyutuna sahip olduğu için(sadece 56-bit) düzenlenen DES Challenge 3’de 22 saatte DES tarafından şifrelenmiş bir metin kırılabilmiş. Bunun üzerine NIST (National Institute of Standards and Technology) 1997 yılında bir çağrıya çıkıyor. Bu çağrı sonucunda toplanan yaklaşımlardan bazıları gereksinimleri karşılamıyor ve 5 yaklaşım 1999 yılında bir nevi final sahnesine çıkıyor. 2000 yılında Rijndael isimli kişinin yaklaşımı 1.sıraya yükseliyor ve 2001 yılında bu yaklaşım standart haline getiriliyor. Ortaya DES’den daha hızlı çalışan, farklı şifreleme anahtar boyutuna(128, 192, 256) sahip bir şifreleme algoritması meydana geliyor.

Hakkında

  • Bu algoritma 128, 192, 256 bit şifreleme anahtar boyutunu desteklemekte. Eğer bir işlemde sizden AES-128 kullanılması talep ediliyorsa, şifreleme anahtar boyutunuz 128bit, AES-192 ise 192bit olmak zorunda.
  • Bu algoritma simetrik bir algoritmadır, simetrik demek şifrelemek ve şifrenin çözülmesi işlemleri aynı şifreleme anahtarı ile yapılıyor demektir. Bir yerde başka bir şifreleme algoritması için asimetrik ifadesine denk gelirseniz, ilgili şifreleme algoritmasında şifreleme ve şifrenin çözülmesi farklı şifreleme anahtarı ile yapılmakta.
  • Şifreleme blok blok yapılmakta. Her blok boyutu 128bit olarak alınmaktadır.

Terminolojik İfadeler

  • Block Chiper : Şifreleme yapılırken kullanılan yöntemde bit bit değil blok blok bir şifreleme yapılıyorsa kullanılan terimdir.
  • Plaintext : Şifreleme yapılacak ifadedir.
  • Key : Şifreleme yapılırken kullanılacak şifreleme anahtarı olarak kullanılacak ifadedir.
  • Round : Şifreleme sürecinde yapılan iterasyon sayısı. Key boyutuna göre değişiklik göstermektedir. 128 için 10 round, 192 için 12 round, 256 için 14 round yapılmaktadır. Bazı kaynaklarda sırasıyla 11, 13, 15 şeklinde görülebilir. İlk round verilen key ile aynı ifade ile sonuçlandığı için.
  • Round Key : Her round sonrasında, ilgili round için toplamda 128bit uzunluğunda her biri 4byte uzunluğuna sahip 4 farklı subkey oluşturulur. Matematiksel ifadesi ‘w’ olarak görebilirsiniz. İlk round sonucunda elde edilen round key ifadesi, sisteme verilen key ile aynı değere sahiptir.
  • Substitution Box (S-Box) : Şifreleme yapılırken kullanılan look-up tablosudur. Forward ve Inverse S-Box olarak 2 farklı tablo mevcuttur.
  • Round Constant : İlgili round’ın numerik değerine göre daha önceden hesaplanmış sabit ifadedir. Look-up tablosu üzerinden değerler alınabilir. Detaylarını göreceğiz.
  • State : Şifrelenecek 4×4’lük 16 byte içeren bir bloktur.
  • Chipher Text : Şifrelenmiş metin.

Şifreleme Aşamaları

1. Şifreleme Anahtarının Açılımı (Key Expansion)

Bu aşamada orjinal key alınarak, her round sonrasında yeni bir round-key oluşturulur. 128bit key için Round Key oluşturma işleminin adımları şu şekildedir. 192 ve 256 bit bir tık daha komplike olacaktır. Daha öncesinde her round sonrasında 128b round-key oluşturulduğundan ve AES-128 için toplam 11 round-key olduğundan bahsetmiştik. Bu durumda 11 x 128b = 176 byte uzunluğuna sahip bir dizi bizim için yeterli olacaktır.

Tüm seri boyunca kullanacağımız key : “AES BLOG YAZIMIZ” olacaktır. Her karakter bir byte olacak şekilde düşünülmüştür. Bu anahtarımızı hex formatına dönüştürelim;

  1. İlk round sonrasında oluşan round-key(w0, w1, w2, w3) chipker key ile aynı değere sahiptir.

  2. Diğer roundlar hesaplanırken eğer sub round-key 4’ün katı değilse şu formül uygulanır;

    w[ i ] = w[i – 1] ^ w[i – 4]

    Örnek : İkinci round’a ait i = 6 sub round-key hesaplanırken;
    w[6] = w[5] ^ w[2] şeklinde olacaktır.

  3. Eğer i % 4 == 0 ise burada süreç değişecektir.

    1. Öncelike Rotword işlemi yapılır. Bu işlemde w[i -1] değeri bir byte sola kaydırılır. Kaydırma işlemi lineer olarak değil, circular olarak gerçekleştirilir.
    2. Yapılan Rotword işlemi sonrasında elde edilen byte’lar S-BOX tablosuna parametre olarak gönderilir. Ve yeni bir dizi elde edilmiş olur.
    3. Yeni elde edilen dizi, ilgili round’ın numerik değerine ait round sabiti ile xor işlemi tabi tutulur.
    4. Elde edilen son değer ile w(i -4) değeri xor işlemine tabi tutulur.

    Örnek : w[4] sub round-key değerini hesaplamak isteyelim ve elimizde ki w[3] : (20, 46, 75, 67) ve w[0] : (54, 68, 61, 74) olsun.
    Circular Kaydırma : (46, 75, 67, 20) olacaktır.
    Byte Substitution (S – Box) : (B7, 5A, 9D, 85) olacaktır (Yukarda bulunan S-BOX tablosuna gönderilerek dönen değerlerden oluşturuldu).
    Round Sabitinin Eklenmesi : Bu round 1. round olduğu için (2 gibi düşünebilir ancak daha önce belirttiğim gibi ilk round direkt olarak chiper key ile eşleştirildiği için 0. round olarak varsayılmakta.) (01, 00, 00, 00) ^ (B7, 5A, 9D, 85) işleminin sonucu (B6, 5A, 9D, 85) olacaktır.

    Son adım ise w[i – 4], i = 4 için w[0] değeri ile bulduğumuz sonucu xor işlemine tabi tutalım. w[4] : (E2, 32, FC, F1) olacaktır.


Şimdi ise kendi chiper key değerimizden round-key değerlerimizi oluşturmak için gerekli fonksiyonu oluşturalım.

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>


#define getSBoxValue(num) (sbox[(num)])
#define KEY_EXP_SIZE 176
#define Swap4Bytes(val) \
 ( (((val) >> 24) & 0x000000FF) | (((val) >>  8) & 0x0000FF00) | \
   (((val) <<  8) & 0x00FF0000) | (((val) << 24) & 0xFF000000) )

uint8_t round_sub_keys[KEY_EXP_SIZE];


static const uint8_t sbox[256] = {
  //0     1    2      3     4    5     6     7      8    9     A      B    C     D     E     F
  0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
  0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
  0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
  0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
  0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
  0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
  0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
  0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
  0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
  0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
  0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
  0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
  0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
  0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
  0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
  0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 };


  static const uint8_t Rcon[11] = {
  0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36 };


static void KeyExpansion(uint8_t* RoundKey, const uint8_t* Key)
{
  unsigned i, j, k;
  uint8_t tempa[4]; // Used for the column/row operations
  const uint8_t Nr = 10;
  const uint8_t Nk = 4;
  const uint8_t Nb = 4;

  // The first round key is the key itself.
  for (i = 0; i < Nk; ++i)
  {
    RoundKey[(i * 4) + 0] = Key[(i * 4) + 0];
    RoundKey[(i * 4) + 1] = Key[(i * 4) + 1];
    RoundKey[(i * 4) + 2] = Key[(i * 4) + 2];
    RoundKey[(i * 4) + 3] = Key[(i * 4) + 3];
  }

  // All other round keys are found from the previous round keys.
  for (i = Nk; i < Nb * (Nr + 1); ++i)
  {
    {
      k = (i - 1) * 4;
      tempa[0]=RoundKey[k + 0];
      tempa[1]=RoundKey[k + 1];
      tempa[2]=RoundKey[k + 2];
      tempa[3]=RoundKey[k + 3];

    }

    if (i % Nk == 0)
    {
      // This function shifts the 4 bytes in a word to the left once.
      // [a0,a1,a2,a3] becomes [a1,a2,a3,a0]

      // Function RotWord()
      {
        const uint8_t u8tmp = tempa[0];
        tempa[0] = tempa[1];
        tempa[1] = tempa[2];
        tempa[2] = tempa[3];
        tempa[3] = u8tmp;
      }

      // SubWord() is a function that takes a four-byte input word and 
      // applies the S-box to each of the four bytes to produce an output word.

      // Function Subword()
      {
        tempa[0] = getSBoxValue(tempa[0]);
        tempa[1] = getSBoxValue(tempa[1]);
        tempa[2] = getSBoxValue(tempa[2]);
        tempa[3] = getSBoxValue(tempa[3]);
      }

      tempa[0] = tempa[0] ^ Rcon[i/Nk];
    }

    j = i * 4; 
    k = (i - Nk) * 4;
    RoundKey[j + 0] = RoundKey[k + 0] ^ tempa[0];
    RoundKey[j + 1] = RoundKey[k + 1] ^ tempa[1];
    RoundKey[j + 2] = RoundKey[k + 2] ^ tempa[2];
    RoundKey[j + 3] = RoundKey[k + 3] ^ tempa[3];
  }
}

/* Check with this online web-site : https://www.cryptool.org/en/cto/aes-step-by-step */

int main(int argc, char* argv[])
{
    uint8_t chiper_key[16] = {0x41, 0x45, 0x53, 0x20, 0x42, 0x4c, 0x4f, 0x47, 0x20, 0x59, 0x41, 0x5a, 0x49, 0x4d, 0x49, 0x5a};

    KeyExpansion(round_sub_keys, chiper_key);

    for (int i = 0; i < 11; ++i)
    {
        printf("Round {%i} -> ", i);

        for (int y = 0; y < 16; ++y)
        {
            static int sub_key_idx = 0;

            if (y % 4 == 0)
            {
                printf("sub-key-w[%i] :", sub_key_idx);
                ++sub_key_idx;
            }

            printf("%02x ",round_sub_keys[i * 16 + y]);
        }

        printf("\r\n");       

    }
}

Round {0} -> sub-key-w[0] :41 45 53 20 sub-key-w[1] :42 4c 4f 47 sub-key-w[2] :20 59 41 5a sub-key-w[3] :49 4d 49 5a
Round {1} -> sub-key-w[4] :a3 7e ed 1b sub-key-w[5] :e1 32 a2 5c sub-key-w[6] :c1 6b e3 06 sub-key-w[7] :88 26 aa 5c
Round {2} -> sub-key-w[8] :56 d2 a7 df sub-key-w[9] :b7 e0 05 83 sub-key-w[10] :76 8b e6 85 sub-key-w[11] :fe ad 4c d9
Round {3} -> sub-key-w[12] :c7 fb 92 64 sub-key-w[13] :70 1b 97 e7 sub-key-w[14] :06 90 71 62 sub-key-w[15] :f8 3d 3d bb
Round {4} -> sub-key-w[16] :e8 dc 78 25 sub-key-w[17] :98 c7 ef c2 sub-key-w[18] :9e 57 9e a0 sub-key-w[19] :66 6a a3 1b
Round {5} -> sub-key-w[20] :fa d6 d7 16 sub-key-w[21] :62 11 38 d4 sub-key-w[22] :fc 46 a6 74 sub-key-w[23] :9a 2c 05 6f
Round {6} -> sub-key-w[24] :ab bd 7f ae sub-key-w[25] :c9 ac 47 7a sub-key-w[26] :35 ea e1 0e sub-key-w[27] :af c6 e4 61
Round {7} -> sub-key-w[28] :5f d4 90 d7 sub-key-w[29] :96 78 d7 ad sub-key-w[30] :a3 92 36 a3 sub-key-w[31] :0c 54 d2 c2
Round {8} -> sub-key-w[32] :ff 61 b5 29 sub-key-w[33] :69 19 62 84 sub-key-w[34] :ca 8b 54 27 sub-key-w[35] :c6 df 86 e5
Round {9} -> sub-key-w[36] :7a 25 6c 9d sub-key-w[37] :13 3c 0e 19 sub-key-w[38] :d9 b7 5a 3e sub-key-w[39] :1f 68 dc db
Round {10} -> sub-key-w[40] :09 a3 d5 5d sub-key-w[41] :1a 9f db 44 sub-key-w[42] :c3 28 81 7a sub-key-w[43] :dc 40 5d a1

Çıktımız yukarda bulunduğu gibi olacaktır. Çıktının sağlaması sitesi üzerinden doğrulanabilir.

Kaynaklar

STM32 Uzaktan Gömülü Yazılım Güncelleme


Merhabalar, bu içeriği bootloader serisinin son yazısı olarak düşünebiliriz. Şayet direkt olarak bu yazıyla karşılaştıysanız ancak konu kümülatif bir şekilde ilerlediği için, şuradan diğer içerikleri incelemeniz sizin için faydalı olacaktır. Daha önce Duru isminde bir non-blocking, kullanımı rahat ve farklı MCU ailelerinden üyelerine port edilmesi mümkün ve rahat olan bir framework çalışmamı yayınlamıştım. Bugün ki uygulamamız yine o yapı üzerinde çalışacaktır.

Ne yapacağımızı daha rahat anlamak adına aşağıda bulunan görseli oluşturdum. Görsel Modemin (UC20 QUECTEL) ayağa kaldırılması gibi süreçleri içermiyor. Periyodik olarak backend tarafında yeni bir yazılım olup olmadığını sorguladığımız kısım özelinden başlıyor.

Belirlediğim formata göre sunucu üzerinde dosya şu şekilde barınmakta.

 can_%u.%u.%u.bin 
      |  | +---- bugfix-id
      |  + ----- minor-id
      + -------- major-id

Dosyanın hazırlanma sürecinde derleyici üzerinde yapılması gerekenlerden daha önce bahsetmiştim. Şimdi ise dosyanın içeriğinden bahsedeyim. ‘projeadı.bin’ dosyası oluştuktan sonra bu dosyayı Hex Editor programı üzerinden açarak dosyanın CRC32’sini buradan hesaplayarak ekliyorum ve ardından yayına verilecek versiyon damgasını 4 byte şeklinde ekliyorum.

Şimdi yazılım aşamasında Duru için gerekli yapıyı hazırlayalım, bunun için öncelikle ilgili callbackleri hazırlayalım. Ayrıca ilgili AT komutlarının kullandığınız modeme göre değişiklik gösterebileceğini unutmayınız.

/* İlgili sunucuya modem üzerinden ftp kanalı açılır */
uint8_t ftp_connect(void* self)
{	
	gsm_machine_debug("\r\n Host Connected !! \r\n");

	struct command_machine *cm = (struct command_machine*)self;
	
	char cr = {0x0D};
	
	char cmd[60] = { 0 };
	
	snprintf(cmd, sizeof(cmd), "AT+QFTPOPEN=\"%s\",%i%c","ftpservername", 21, cr);

	cm -> uart_tx_cb((uint8_t*)cmd, strlen(cmd));
	
	return true;
}
/* Sunucu üzerinde bulunan dosyalar listelenir, ilgili dosyanın olması durumunda yeni versiyon olup olmadığı flash üzerinde saklanılan adresten sorgulanır */
uint8_t parse_list_file (void* self, const char* buf, char* record_buf)
{
	if (self != NULL && strlen(buf) > 0)
	{
		char* ret = NULL;
		
		ret = strstr(buf, "CONNECT\r\n");
		
		if (ret != NULL)
		{
			ret = ret + sizeof("CONNECT\r\n");
			
			int i = 0;
			
			memset(gsm_uart.update_file_name, 0, sizeof(gsm_uart.update_file_name));
			
			while (*ret != '\r')
			{
					gsm_uart.update_file_name[i] = *ret++;
					i++;
			}

			ret = strstr(gsm_uart.update_file_name, "can_");
			
			if (ret != NULL)
			{
				ret = ret + sizeof("can");
				
				sscanf(ret, "%u.%u.%u", &gsm_uart.major, &gsm_uart.minor, &gsm_uart.bugfix);
				
				uint32_t current_version = *(uint32_t* )VERSION_ADDR;
				
				if (current_version == 0xFFFFFFFF)
				{
					is_any_new_firmware = true;
				}
				
				else
				{
					uint8_t major = current_version >> 24;
					uint8_t minor = current_version >> 16;
					uint8_t bug_fix = current_version >> 8;			
					
					char cv[10] = {0};
				
					snprintf(cv, sizeof(current_version), "%u.%u.%u", major, minor, bug_fix);
				
					int ret_version = comp_version(cv, ret);
					
					if (ret < 0)
					{
						is_any_new_firmware = true;
					}
					
					//Yeni update degil ise direkt olarak çik.
					else
					{
						struct command_machine* cm = (struct command_machine*)self;
						cm -> command_index = cm -> command_index + 3;
					}
				}
			}
		}	
	}
		
	return 0;
}
/* Dosya Modemin RAM bölgesine indirilir */
uint8_t ftp_get_file(void* self)
{
	
	gsm_machine_debug("\r\n Host Connected !! \r\n");

	struct command_machine *cm = (struct command_machine*)self;

	char cmd[60] = { 0 };
	
	snprintf(cmd, sizeof(cmd), "AT+QFTPGET=\"%s\",\"RAM:can.bin\"\r\n", gsm_uart.update_file_name);

	cm -> uart_tx_cb((uint8_t*)cmd, strlen(cmd));
	
	return true;
}
/* Dosyanın boyutu sunucudan istenilir */
uint8_t ftp_get_size(void* self)
{
	
	gsm_machine_debug("\r\n Host Connected !! \r\n");

	struct command_machine *cm = (struct command_machine*)self;
		
	char cmd[60] = { 0 };	
	
	snprintf(cmd, sizeof(cmd), "AT+QFTPSIZE=\"%s\"\r\n", gsm_uart.update_file_name);

	cm -> uart_tx_cb((uint8_t*)cmd, strlen(cmd));
	
	return true;
}
extern bool is_any_new_firmware;
extern float file_size;

/* Dosyanın boyutu ve dosya sorgulanır */
uint8_t ftp_file_size_cb(void* self, const char* buf, char* record_buf)
{
	if (self != NULL && strlen(buf) > 0)
	{
		char* ret = NULL;
		
		ret = strstr(buf, "OK\r\n\r\n");
		
		if (ret != NULL)
		{
			if (strstr(ret, "627") || strstr(ret, "550"))
			{
				is_any_new_firmware = false;
			}
			
			else 
			{
				ret = strstr(ret, "0,");
				
				if (ret)
				{
					ret = ret + 2;
					
					file_size = atof(ret);
					
					is_any_new_firmware = (file_size > 0) ? true : false;
				}
			}
		}
	}
	
	return 0;
}
/* Versiyonlar karşılaştırılır */
static int comp_version (const char* cv_version1, const char* version2 ) {
    unsigned major1 = 0, minor1 = 0, bugfix1 = 0;
	
    unsigned major2 = 0, minor2 = 0, bugfix2 = 0;
	
    sscanf(cv_version1, "%u.%u.%u", &major1, &minor1, &bugfix1);
	
    sscanf(version2, "%u.%u.%u", &major2, &minor2, &bugfix2);
	
    if (major1 < major2) return -1;
    if (major1 > major2) return 1;
    if (minor1 < minor2) return -1;
    if (minor1 > minor2) return 1;
    if (bugfix1 < bugfix2) return -1;
    if (bugfix1 > bugfix2) return 1;
    return 0;
}
/* Eğer herhangi bir güncelleme yok ise sorgulama durdulur */
int reset (void* self)
{
	if (self != NULL)
	{		
		struct command_machine* cm = (struct command_machine*)self;
		
		cm -> command_index = cm -> command_index + 3;
		
		//machine_stop_current(self);
	}
	
	return 0;	
}

Gelelim ilgili komutları ve komut pencerelerini hazırlamaya,

struct command ftp_open = {"FTP", "QFTPOPEN: 0,0\r\n", NULL, NULL, NULL, 2000, &ftp_connect };

struct command ftp_set_current_dir = {"AT+QFTPCWD=\"/\"\r\n", "QFTPCWD: 0,0", NULL, NULL, NULL, 3000, NULL };

struct command ftp_get_list_file_names = {"AT+QFTPNLST=\"/\",\"COM:\"\r\n", "+QFTPNLST:", NULL, NULL, &parse_list_file, 3000, NULL, reset};

struct command ftp_get_file_size = {"get_size", "QFTPSIZE:", NULL, NULL, &ftp_file_size_cb, 3000, &ftp_get_size };

struct command ftp_get_file_to_ram = {"get_file", "QFTPGET:", NULL, NULL, NULL, 3000, &ftp_get_file };

struct command ftp_close = {"AT+QFTPCLOSE\r\n", "+QFTPCLOSE: 0,0", NULL, NULL, NULL, 1500, NULL };

struct command ftp_deactivate_pdp = {"AT+QIDEACT=1\r\n", "OK\r\n", NULL, NULL, NULL, 4000, NULL };
const struct command* ftp_command_list_table[] = 
{
	&ftp_open,
	&ftp_set_current_dir,
	&ftp_get_list_file_names,
	&ftp_get_file_size,
	&ftp_get_file_to_ram,
	&ftp_close,
	&ftp_deactivate_pdp,	
};

Ve yeni bir güncelleme var ise artık dosyamızı sd karta yazdırabiliriz.

		if (is_any_new_firmware  && get_command_window_status(&command_machine_t, "FTP"))
		{
			// Herhangi bir yarım kalma durumları için dosya var olup olmaması sınanır.
			if ((fr = f_open(&file, "can.bin\0", FA_READ)) == FR_NO_FILE)
			{
					//Nothing.
			}
			
			else
			{
				f_close(&file);
				
				f_unlink("can.bin\0");
			}
			
			temp = file_size / 1000;
			
			temp_remainig = (file_size) - (temp * 1000);
		
			uart_tx_platform_specific((uint8_t *)"AT+QFOPEN=\"RAM:can.bin\",2\r\n", strlen("AT+QFOPEN=\"RAM:can.bin\",2\r\n"));
		
			HAL_Delay(500);
			
			while(temp-- > -1)
			{		
				int data_size = temp > -1 ? 1000 : temp_remainig;
			
				char temp_buf[75] = { 0 };
				
				snprintf(temp_buf, sizeof(temp_buf), "AT+QFREAD=3000,%d\r\n", 1000);
				
				clear_gsm_uart(&gsm_uart);	
				
				uart_tx_platform_specific((uint8_t *)temp_buf, strlen(temp_buf));
				
				HAL_Delay(1500);
			
				if (strlen(gsm_uart.buffer) > 0)
				{						
					char* ret = NULL;
					
					ret = strstr(gsm_uart.buffer, "CONNECT");
					
					if (ret != NULL)
					{
						ret = ret + sizeof("CONNECT");
						
						while (*ret++ != '\n');
						
						if ((fr = f_open(&file,(char *)"can.bin\0", FA_OPEN_APPEND | FA_WRITE)) == FR_OK)
						{												
							UINT bw;
							
							f_write(&file, ret, data_size, &bw);  
							
							fr = f_close(&file);	
						}						
					}
				}
			}
			
			NVIC_SystemReset();
		}

Aynı MCU içerisinde daha önce yazmış olduğum STM32 SD KART ÜZERİNDEN BOOT ETMEK içeriğinde bulunan yazılım her reset sonrasında çalıştırılmakta.

Eğer modeminiz destekliyorsa, ilgili dosyayı modemin RAM bölgesine indirilmesini sağlamak gerçekten güzel bir kolaylık sağlamakta. Buna ek olarak ilgili SSL sertifikalarını kullanarak rahatlıkla FTPS işlemini de yapabiliyorsunuz.

Sözün özü; Duru kütüphanesini kullanarak güncelleme yapmak bu kadar basit 🙂

Herkese iyi çalışmalar dilerim.

Knuth–Morris–Pratt Algoritması | Mülakatlar [Codility – Hackerrank – Qualified.io]


Bazı yaklaşım metodlarını, düşünce aşamalarını ilgili konularla ilişkilendiren yapılarda pratik yapmak, yeni gelecek problemlerde artık sorunun çözümünü omurilik soğanına aktarmak için birebir. Her şeyin anahtarı pratik ve pratik. Kimisi için 3 pratik, kimisi için 5 pratik ama pratik.

İnsanın da gördüğü, deneyimlediği kadar bir varlık olduğunu düşünürsek bugün ben de sizlere KMP algoritmasını ve beraberinde bir firmanın mülakatında sorulmuş olan bir soruyu KMP kullanarak ve kullanmayarak çözmeye çalışacağım. Karakter gruplarının bir araya geldiği ifadelerde işlem yapmak – arama, silme, değiştirme gibi günün sonunda belli bir indekse dayandırılması gereken işlemler – zahmetli olabiliyor. Ama aranacak ifade özelinde bir lut oluşturabilirsek ve bu oluşturduğumuz lut ile birlikte asıl aradığımız yere gidersek süreçleri iyileştirebilmekteyiz. Dijital bir sistem üzerinde çalıştığımızı düşünürsek iyileştirdiğimiz şey algoritma karmaşıklığı olacaktır.

Yukarda ifade etmeye çalıştığım gibi KMP algoritması iki aşamadan oluşmakta;

  1. Öncelikle pattern(aranacak ifade) için bir lut oluşturmak.
  2. Text içerisinde arama yaparken lut’u kullanmak.

Nasıl bir metin içerisinde arayacağımızı şu an için düşünmeden, elimizde şöyle bir pattern olduğunu varsayalım.

aabaabaaa
pattern

Bu pattern içerisinde görebileceğiniz gibi kendini parçalı halde kendini tekrar eden bazı bölümler bulunmakta. Yani KMP algoritmasını uygulamak isteyeceğiniz yapıların böyle olması önem arz etmekte. Standart arama algoritmalarından daha verimli sonuçlar verme durumunu ortayan çıkaran bu zaten. Çünkü patternin kendisinde oluşturduğunuz lut sayesinde, arama yaparken eşleşmeyen bir karakter sonrasında tüm süreci başa almak yerine pattern içerisinde hangi karaktere dönüş yapacağımızı belirlemiş oluyoruz. Öncelikle bu lut nasıl oluşturuluyor buna bakalım.

Pattern içerisinde gezinirken iki farklı index değeri tutuyoruz. Bunlar i ve j diyelim. j değerimizin başlangıç değeri 0 ve i değerimizin başlangıç değeri ise 1 olarak başlasın. Buna ek olarak pattern ilk indexli elemanın lut içerisinde ki değeri her zaman 0 olarak başlamalı. Şimdi adım adım bu lut’u oluşturalım.

1.Adım

aabaabaaa
000000000

1.adımda i = 1, j = 0 değeri ile başlamakta. Ve tüm lut şu anda 0 olarak işaretlenmiş. Bir sonraki adım içerisinde i ve j’nin sahip olduğu değerler pattern’e index olarak verilir ve karşılaştırılma yapılır. Eğer karşılaşma sonrası eşleşme meydana gelirse, i’nin lut index değeri j + 1 olarak belirlenir i ve j değeri bir arttırılır. Şayet herhangi bir eşleşme bulunmaz ise bizim için j’nin değerinin 0’a eşit olması ya da olmaması önemli. O kısma geleceğiz.

2.Adım

aabaabaaa
010000000

1.adım açıklama kısmında yazdığımız gibi, şöyle bir kod parçacığı oluşmakta.

if (pattern[j] == pattern[i])
{
      table_array[i] = j + 1;
      i++;
      j++;
}

Karşılaştırmaya pattern sonuna gelene kadar devam ediyoruz, 3.adımda pattern[1] ve pattern[2] karakterlerinde bir karşılaştırma yapacağız. Eğer karakterlerin eşit olmaması durumunda şayet j değeri sıfırdan farklı bir değer ise pattern içerisinde bir tekrar olup olmadığını anlayabilmek adına j’nin yeni değerini lut tablosunda mevcut j değerinin index değerinde bulunan ifadeden bir önceki değere eşitliyoruz, yani şöyle bir kod parçacığı oluşmakta.

if (j != 0)
{
      j = table_array[j - 1];
}

3.Adım

aabaabaaa
010000000

2.Adım içerisinde bahsettiğimiz üzere yapılan karşılaştırma sonrasında j değeri şu anda 0 olmuş durumda çünkü table_array[0] değerimiz 0’dı. i değeri ise 2 olarak durmakta. Bu döngünün bir while içerisinde döndüğünü düşünürsek şu anda pattern[0] ve pattern[2] üzerinde bir karşılaştırma yapılmakta. Burada yeni bir durum çıkmakta. Şayet j değeri 0’a eşit ise lut içerisinde ki i’nin lut index değeri 0 olmakta. Yani şöyle bir kod parçacığı oluşmakta.

if (j != 0) 
{
       j = table_array[j - 1];
}

else
{                
       table_array[i] = 0;
       i++;     
}

Buradan anlayabiliriz ki ilgili index ve öncesinin başlangıcında herhangi bir şekilde tekrar bulunmamakta. Bir adım daha yapacağım ve diğer adımları size bırakacağım.

4.Adım

Karşılaştırmaya devam ediyoruz şu anda j değerimiz 0, i değerimiz ise 3 olmuş durumda. pattern[0] ve pattern[3] arasında bir eşleşme olduğuna göre, 1.adımda ki süreç gerçekleşiyor.

aabaabaaa
010100000

Tüm bu adımlar sonrasında lut şu halini almakta.

010123452

2. aşama olan arama kısmında benzer bir süreç gerçekleşmekte. Eğer j kısmı verilen pattern uzunluğuna eşit ise pattern bulunmuş demektir ve i – j değeri bizim aradığım text içerisinde patternin başladığı indexi döndürmekte. Tüm bu süreçleri ve ilgili örneğin koda dökülmüş haline şuradan erişebilirsiniz.

Şimdi gelelim bir mülakatta sorulmuş bir soruyu çözmeye çalışalım. Bu soruda KMP kullanmak zorunda elbette değiliz. Nedenini zaten lut oluşturduğumuzda görebiliriz.

We are given a string S of length N consisting only of letters ‘A’ and/or ‘B’. Our goal is to obtain a string in the format “A…AB B” (all letters ‘A’ occur before all letters ‘B’) by deleting some letters from S. In particular, strings consisting only of letters ‘A’ or only of letters ‘B’ fit this format.

Examples :

Given S = “BAAABABA”, the function should return 2.

Given S = “BBABAA”, the function should return 3.

Given S = “AABBBB”, the function should return 0.

İnsanın karşısına string algoritmaları ile ilgili bir soru geldiğinde ve belli bir süre içerisinde bir çözüm sağlaması gerektiğinde hatta ve hatta karşı taraf sesli düşünerek biz çözüm istiyorsa fakat daha önce çok fazla pratik yapılmamış ise biraz dumor olabiliyor. Belki daha öncesinde öğrenilen bir yaklaşım mevcut soru için verilen doğruluk, zaman karmaşıklığı gibi çözüm parametreleri açısında iyi olmayabilir ama o yaklaşım sayesinde farklı bir yaklaşım sergileyebiliriz.

Ben kendi çözümümden bahsedeceğim, belki farklı yaklaşımlara sahip olabilirsiniz ki bu çok normal. Bu soruda ilk olarak bb ve ba gibi patternin varlığının olup olmamasını sınamak benim için doğru bir yaklaşım gibi geldi. Bu yaklaşımda önemli olan şu eğer bb patterni mevcut ise şöyle bir şey gelebilir bba ama ba patterni mevcut ise başlangıçta minumum 4 karakterlik bir arayış gerçekleşmesi gerekir. Buna ek olarak bb patterni mevcut ise bu pattern öncesinde ba gibi patternin varlığı gerçekleşmiş ya da devamında gerçekleşecek mi ?

Benim bu soru özelinde gerçekleştirmiş olduğum ve kmp kullandığım yapıyı şuradan görebilirsiniz. Aklınıza gelen bir test vakasını denediğinizde bir problem var ise bildirirseniz çok sevinirim. Peki bu soruda KMP kullanmak mantıklı mı ? Çözümü incelerseniz KMP yaklaşımında ufak tefek bazı değişikler yapıldığını görebilirsiniz. Soruya bir çözüm sağlayıp rahatladıktan sonra tekrar bakıldığında aslında lut tablosu bb patterni için 0 1 ba patterni için 0 0 değerini almakta. Ve arama kısmında bu lut tablosu çokta işlevsel değil. Tekrar düşünüldüğünde ortaya şu yapı çıkmakta. İşte bahsetmeye çalıştığım olay bu aslında KMP algoritmasını bilmiyor olsaydım o konuda bir pratiğim olmasaydı belki de bu yaklaşımı yapamıyor olabilirdim.

Pratik, pratik, pratik yazılımın anahtarı bence bu. Bir sonraki string algoritmasında görüşmek üzere.

Raspberry Pi | Buildroot | QT Projesi


Buildroot Nedir ?

Buildroot, gömülü bir sistem için eksiksiz bir şekilde Linux sistemi oluşturma sürecini basitleştiren ve bu süreci otomatikleştiren açık kaynak bir araçtır. Hedef sistem için, çapraz derleme araçları, kök dosya sistemi, kernel imajı ve bootloader üretebilir. Tüm bunları hedef sistem için birbirinden bağımsız şekilde de yapmanıza olanak sağlar. Örneğin çapraz derleme yapılabilmesi için ilgili araçlara sahip olduğunuz bir hedef sisteme sadece kök dosya sistemi buildroot üzerinden ürettirilebilir. Farklı bir çok hedef sistem ve versiyonları için destek vermektedir.

Buildroot aracını windows işletim sistemi üzerinde çalıştırabilir miyim ?

Hayır, buildroot linux sistemler üzerinde koşabilecek şekilde tasarlanmıştır. Windows makineniz üzerine kuracağınız bir sanal makine üzerinden çalışmalarınızı yapabilirsiniz. Bu yazının hazırlanma sürecinde kullanılan linux dağıtımı ve versiyonu Ubuntu 18.04.5 LTS‘dir.

Buildroot aracını kullanmak için sistemde bulunması gerekli olan araçlar var mıdır ?

Evet, buildroot ilgili bir çok paketi kendisi oluşturacak olmasına rağmen bazı sistem gereksinimleri vardır. Host sistem içerisinde olması zorunlu paketler şunladır ;

  • which
  • sed
  • make (version 3.81 or any later)
  • binutils
  • build-essential (only for Debian based systems)
  • gcc (version 4.8 or any later)
  • g++ (version 4.8 or any later)
  • bash
  • patch
  • gzip
  • bzip2
  • perl (version 5.8.7 or any later)
  • tar
  • cpio
  • unzip
  • rsync
  • file (must be in /usr/bin/file)
  • bc

Yukarda bahsi geçen araçlara ek olarak opsiyonel olarak yükleyebileceğiniz bazı araçlarda vardır.

İlgili araçların kurulumları için apt programını kullanabiliriz. (apt vs apt-get için ilgili yazıyı okuyabilirsiniz)

sudo apt install sed make binutils gcc g++ bash patch \gzip bzip2 perl tar cpio python unzip rsync wget libncurses-dev

Eğer 64 bit platform üzerinde çalışıyor iseniz, şu komutu da çalıştırmanız gerekmektedir.

apt-get install gcc-multilib

Buildroot aracına nereden ulaşabilirim ?

https://buildroot.org/download.html sitesine giderek Long Term Support versiyonunu linux tabanlı sisteminize indirmeniz gerekmektedir. İndirme işleminden sonra tüm dosyaları çalışmak istediğiniz bir dizine çıkartabilirsiniz.

volkan@volkan:~/Downloads$ tar -xvf buildroot-2020.02.10.tar.gz -C ~/buildroot_working_dir

Yukarda bulunan komut ile Downloads klasörü altına indirmiş olduğum buildroot-2020.02.10.tar.gz dosyasını home dizini altında buildroot_working_dir klasörü içerisine çıkartıyorum. Tar programının almış olduğu komut satırı argümanları XVF sırasıyla, Extraxt, Verbose, File anlamlarına gelmektedir. Eğer C argümanı olmasaydı bulunduğu dizin içerisine çıkartacaktı. İlgili komut ile başka bir dizine dosyaları çıkarmasını sağladık. Bu argüman sonrasına aldığı dizin eğer custom bir dizin ise yukarda örnekte olduğu gibi, daha önce oluşturulmuş olması gerekiyor. Aksi takdirde belirtilen dizin için Cannot read : Is a directory hatasını alabilrsiniz.

Bu süreçten sonra ilgili dizine geçiş yaparak artık orada çalışmanız gerektiğini unutmayın !

Hedef sistem için konfigürasyon oluşturmak

Bu aşamada mevcut konfigürasyonları bir template gibi görebilir ve onların üzerinden devam edebileceğiniz gibi, sıfırdan kendi konfigürasyon dosyasınıza oluşturabilirsiniz. İlgili tüm konfigürasyonlar configs/ dizini altında bulunmaktadır. Kendi donanım sürecimde Raspberry Pi 3 modelini kullandığım ve QT gibi paketlerin seçilmiş olmasını tercih ettiğim için;

make raspberrypi3_qt5we_defconfig

komutunu giriyorum. Bu komutun başarıyla sonuçlandığını gösteren çıktı;

#
# configuration written to /home/volkan/buildroot_working_dir/buildroot-2020.02.10/.config
#

Şimdi oluşturacağımız dağıtıma ekleyeceğimiz paketlerin seçimini yapabileceğimiz bir pencereye geçiş yapabiliriz. Bu yapılan seçimler belirlediğimiz konfigürasyon üzerine yazılacaktır. Geçiş yapacağımız pencere curses tabanlı bir arayüz olup, kullanımı oldukça rahattır. Bunun için;

make menuconfig

Sizi karşılayan pencerenin görselini yukarda görebilirsiniz. Kendi(host) sistemimiz üzerinden hedef(target) sisteme yazılım üretebilmek çalışmasına kabaca çapraz-derleme(cross-compile) denmektedir. Bu çalışmayı yapabilmemiz için gerekli toolchainleri kendi makinemizde göstermemiz gerekiyor. Bu yüzden ilk aşama Toolchain sürecini yönetmek, 3.seçenek olan Toolchain’e geçiş yapalım.

Sizi karşılayacak pencere;

Çapraz Derleme Toolchain’i konusunda buildroot iki tane çözüm sunmakta ve bunlar sırasıyla. Buildroot Toolchain ve External Toolchain‘dir. Default olarak Buildroot Toolchain seçili durumdadır. İkisi arasından seçiminizi Toolchain type (Buildroot toolchain) menüsü üzerinden yapabilirsiniz. Bugün External Toolchain seçimini yaparak devam ediyoruz. Bunu seçmemizin sebebi, iyi bilinen ve test edilmiş bir toolchain seçimi yapmak. Eğer Buildroot Toolchain seçili kalsaydı, yapılacak konfigürasyonlara özgü buildroot gerekli toolchain’i kendisi derleyip oluşturacaktı. External Toolchain seçimi yapıldıktan sonra görebileceğiniz pencere;

Dilerseniz Toolchain kısmında Linaro Toolchain‘i seçebilirsiniz ben Arm ARM olarak devam ediyorum. Görsel de seçili değil ancak ek olarak, debug yapmanızın gerektirdiği durumlarda ihtiyaç duyacağımız Copy gdb server to the Target seçeneğini de ekleyebiliriz.

Ana menüye dönerek sistem bazlı ayarlarımızı yapmak için, System Configuration penceresine dallanabiliriz.

Sizi karşılayacak pencere;

Bu pencere içerisinde System hostname, System banner, Root password gibi ayarları yapabilirsiniz. Buna ek olarak /dev seçeneği altında buildroot 4 farkı opsiyon sunmaktadır. Cihazlar eklenip kaldırıldığında kullanıcı alanının bilgilendirmesi istenildiğinde Dynamic using devtmpfs + mdev seçeneğinin seçilmesini iyi bir çözüm olduğu belirtilmiş. Default durumda o şekilde gelmekte ve bende ayarlarını değiştirmedim. Buna ek olarak pencere alt tarafa gittikçe görebileceğiniz Install timezone info seçeneğini de aktif edebilirsiniz.

Şimdi Kernel penceresinde herhangi bir değişiklik yapmadığım için o kısmı geçiyorum. Dilerseniz inceleyebilirsiniz.

Artık sisteme ekleyeceğimiz uygulamaları seçebiliriz. Bunu ekleyebileceğimiz sekme Target Packages sekmesidir. Bu sekme içerisinde bir çok paket bulunmakta. Şayet paketin adını biliyorsanız “/” tuşu ile arama penceresine geçiş yapabilir ve paket adını yazarak hangi menü dallanmasından sonra ilgili paketi aktif edeceğinizi görebilirsiniz. Örneğin nano editörünü kurmak istiyoruz;

Arama yardımıyla, bu text editörün, Target Packages -> Text editor and viewers altında bulunduğunu görebildik. Bu şekilde ilgil dizine giderek ekleme yapabilirsiniz.

QT modülünün eklenmesinde bir kaç ince detay olduğu için, onları birlikte yapalım. Öncelikle Target Packages -> Graphic libraries and applications sekmesine giderek Qt5 seçeneğini bulalım. Eğer benimle aynı konfigürasyon dosyasını belirlediyseniz, Qt5 sisteminizde ekli olarak gözükecektir. Qt5 içerisine girerek uygulamarınızda kullanmayı düşündüğünüz kütüphaneleri seçmeniz gerekiyor. Bazıları seçili gelirken bazı paketler seçili gelmeyecektir. Örneğin, seri port işlemleri içeren qt ortamında bir yazılım geliştiriyorsanız, qt5serialport paketini seçmeniz gerekmektedir.

Not : Eğer benimle aynı konfigürasyonu seçmediyseniz ve arayüz tabanlı bir uygulama geliştirecekseniz gui module‘u seçmeniz gerekmektedir. Ayrıca Raspberry Pi videocore eglfs destekli çalıştığı için, Default Graphical Platform olarak eglfs‘i seçmeniz gerekmektedir.

Yukarda bahsettiğim Raspberry Pi’nin videocore sürücüsünü kullanabilmek için, (farklı bir konfigürasyon seçtiyseniz ya da kendi herhangi bir template konfigürasyon seçmediyseniz) Target Packages -> Hardware Handling -> rpi-userland seçeneğini aktif etmeniz gerekmektedir.

Burada tüm paketleri tek tek açıklamam mümkün değil, ihtiyaca göre şekilleniyor gördüğünüz gibi. İçerisinde binlerce paket bulunmakta, eğer dokunmatik ekran kullanıyorsanız Tslib seçeneğini aktif edebilirsiniz. Ya da kablosuz ağlara bağlanmak istiyorsanız, Target Packages -> Networking applications -> wpa_supplicant paketini de eklemeniz gerekmektedir.

Önemli olduğunu düşündüğüm ve bu yazının devamında yapacağımız proje için zorunlu olan bir ayara değineceğim. Bugün QT üzerinden gerçekleştireceğimiz bir projede, nöbetçi eczaneleri ekran üzerinde listelemeye çalışacağız. Kullanacağımız API, HTTPS üzerinden çalıştığı için QT5’e SSL paketini eklememiz gerekmektedir. Şayet bu şekilde bir derleme yapmazsanız, HTTPS kullandığınız istekleriniz başarısız olacaktır. Bunu eklemek için, Target Packages -> Graphic libraries and applications içerisine giderek QT5 penceresine girelim. Burada Custom configuration options kısmına -openssl yazmanız gerekmektedir.

Sistemimiz için gerekli konfigürasyonları yapmış durumdayız. Exit diyerek tüm pencerelerden çıktığımızda konfigürasyonlarımızı kaydetmek istediğimizi belirterek ayrılıyoruz.

Daha önce seçmiş olduğumuz konfigürasyon üzerine yeni konfigürasyonlarımız yazılacaktır. Bu konfigürasyon dosyanızı saklamanızda fayda var. Ekibe yeni katılan ya da birlikte çalışacağınız arkadaşlarınıza bu konfigürasyon dosyasını iletmeniz gerekebilir.

Şimdi ise derleme sürecini başlatabiliriz, bu süreç boyunca sağlıklı bir internet bağlantı hızına sahip olmanız gerekmektedir. Seçtiğiniz paketlere göre ve belirlediğiniz paralel derleme argümanınıza göre bu süreç elbette değişkenlik gösterebilir. Sanal makine kullanıyorsanız ayırdığınız disk boyutunun yeterli olması gerektiğini unutmayın. Eğer yeterli değilse, bir süre sonra aşağıda bulunan hataya benzer hatalar alabilirsiniz.

cc1: out of memory allocating 66574076 bytes after a total of 148316160 bytes
make

komutu ile derleme işlemini başlatabiliriz. Eğer paralel derleme yapmak istiyorsanız, örneğin;

make -j4

4 çekirdek üzerine bu süreci bölebilirsiniz. Burada önemli olan sisteminizin bu çekirdek sayısını destekliyor olup olmadığıdır. Şayet windows makineniz üzerinde sanal makine kurmuşsanız ve bu makineyi tek çekirdek olarak belirlediyseniz, yazdığınız 4’ün bir anlamı olmayacaktır. Aşağıda bulunan komutu kullarak, linux makineniz için Core(s) per socket değerini görebilirsiniz.

lscpu

Derleme süreci sonrasında, çalıştığınız dizin içerisinde /output/images altında sdcard.img ismiyle oluşacaktır. Bu dosyayı sd karta yazdırmak için ben balenaEtcher programını kullandım, dilerseniz bu adımı komut satırı üzerinden de gerçekleştirebilirsiniz. Tamamen size kalmış bir durum. Yazdırma işleminden sonra, kartınızı Raspberry Pi’ye takarak testinize gerçekleştirebilirsiniz.

Donanımınıza ssh üzerinden root olarak erişim sağlamanız gerekecektir. Bunun için, sshd_config dosyası içerisinde bulunan PermitRootLogin‘i YES olarak değiştirmeniz gerekmektedir.

Raspberry pi üzerinde bulunan debug uart /dev/ttyAMA0 portunu kendi uygulamarınızda kullanmak istiyorsanız yapmanız gereken bazı ayarlar var. Eğer böyle bir şeye ihtiyacınız yok ise bu adımı geçebilirsiniz.

Bunun için, öncelikle sistem dosyalarını mount etmemiz gerekmekte. Bunun için mount komutunu kullanacağız, –t komut satırı argümanı ile öncelikle dosya formatını giriyoruz. Daha sonrasında nereden nereye mount edeceğimiz bilgisini giriyoruz. mmcblk0 raspberry pi’nin kendi sistemini bulundurduğu yer ve bunu sd kart ile eşleştirmemiz gerekiyor.

mount -t vfat /dev/mmcblk0p1 /mnt

Ek not : board/raspberrypi3.cfp içerisinde hangi dosyaların görüntülenebilmesi için mount edilmesi gerektiği yazmakta.

image boot.vfat {
vfat {
files = {
“bcm2710-rpi-3-b.dtb”,
“bcm2710-rpi-3-b-plus.dtb”,
“bcm2710-rpi-cm3.dtb”,
“rpi-firmware/bootcode.bin”,
“rpi-firmware/cmdline.txt”,
“rpi-firmware/config.txt”,
“rpi-firmware/fixup.dat”,
“rpi-firmware/start.elf”,
“rpi-firmware/overlays”,
“zImage”
}
}
size = 32M
}
….

Şimdi ise, nano /mnt/config.txt dosyasına giderek, son satıra ekleme yapıyoruz.

enable_uart=1

Daha sonra inittab içerisinde getty’i kapatmanız gerekiyor. Bunun için nano /etc/inittab dosyasına giderek,

console::respawn:/sbin/getty/ -L console 0 vt100

ilgili satırı yorum satırı haline getirelim.

Tüm bu adımlardan sonra sisteminizi tekrar başlatmanız gerekir.

Host Tarafında Bulunan QT’de Target Platform İçin Kit Oluşturmak

Tüm bu süreçlerden sonra bizim için önemli olan, çapraz derleme yapmamızı sağlayacak ayarları artık host tarafında yapabiliriz. QT Creator programını açarak Tools -> Option -> Build & Run -> Qt Versions sekmesine gidiyoruz. Daha önce derlediğimiz buildroot klasörümüzün olduğu yere giderek /output/host/bin/qmake dosyasını ekliyoruz.

Daha sonra Compilers sekmesine giderek, ADD → GCC → C yaparak, output/host/bin/arm-none-linux-gnueabihf-gcc ekliyoruz, tabi Cpp için de gerekli toolchain dosyasını vermek gerek. Yine aynı şekilde ADD → GCC → C++ dedikten sonra output/host/bin/arm-none-linux-gnueabihf-g++ seçiyoruz.

Yukarda bulunan adımları yaptıktan sonra Kits sekmesinden yeni bir kit ekliyoruz, Kit ekleme bölümünde bulunan yapıda

  • Device Type olarak → Generic Linux Device
  • Sysroot tarafında, output/host/arm-buildroot-linux-gnueabihf/sysroot klasörünü gösteriyoruz.
  • Compiler sekmesi altında bulunan kısımda daha önce eklediğimiz C ve C++’ veriyoruz.
  • Qt version : host olacak şekilde seçim yapıyoruz.

Artık Devices sekmesi altında yazacağımız kodu raspberry pi üzerinde çalıştırmak için gerekli hostname, username, passowrd kısımlarını giriyoruz. Device olarak eklediğiniz kiti seçmeniz gerekir. Bu işlemlerden sonra isterseniz Test sekmesine basarak test yapabilirsiniz. Önemli bir not, sanal makine üzerinde çalışıyor ve ethernet kartını bridge olarak seçmemişseniz, raspberry pi ile aynı ağda olmayacağınız için bağlantı sağlanmayacaktır. O yüzden sanal makinenizin ayarları üzerinden köprülemeyi unutmayın.

Daha sonra açıklayacağım ama sd kart üzerinde yapmanız gereken bir işlem bulunmaktadır. Host tarafında geliştirdiğiniz projenizi, target platforma gönderdikten sonra uygulamayı çalıştırdığınızda free disk ile ilgili bir hata alacaksınız. Bu durumda sd kartın resize yapılması gerekiyor. Çünkü buildroot üzerinden oluşturduğunuz sistem tam fit olacak şekilde bir imaj oluşturmakta. Konfigürasyon dosyanızı incelerseniz şöyle bir ibare görülebilir.

BR2_TARGET_ROOTFS_EXT2_SIZE="400M"

Gparted programı ile resize işlemini yapabilirsiniz. Grafik arayüze sahip olduğu için kullanımı oldukça kolay. İlgili storage seçimini yaptıktan sonra (/dev/sdb2 , ext4 ile formatlı yer burası. Diğer taraf rootfs, oraya herhangi bir şey yapmayın.) kendiniz istediğiniz kadar yer ayırabilirsiniz.

QT’de Proje Oluşturma Süreçleri ve Nöbetçi Eczane Uygulaması

QT üzerinde her zaman yaptığınız gibi projenizi oluşturabilirsiniz, projeyi oluştururken dikkat etmeniz gereken kısım kit olarak daha önceden oluşturduğumuz kiti seçmelisiniz. Oluşturduğunuz proje için Project sekmesinden Shadow build seçeneğini kapatmanız gerekmektedir.

Daha sonra .pro uzantılı dosyanıza aşağıda bulunan ayarı eklemeniz gerekir. Bu ayar sayesinde, projenizi deploy ettiğiniz zaman target platformda root altına gidecektir.

target.path = /root
INSTALLS += target

Nöbetçi eczane listesini çekebilmek için https://collectapi.com/tr/api/health/pharmacy-api sitesinde bulunan API’yi kullanabiliriz. Siteye üye olduktan sonra, kullanmak istediğiniz API’ye gittiğinizde Free modele abone olabilirsiniz. Size 100 tane istek yapmanıza olanak sağlıyor. Profiliniz kısmından hesabınızla ilişkilendirilmiş token’ı görebilirsiniz.

API’nin kullanımı site üzerinden görebiliriz. QML içerisinde kod yazımı gerçekleştireceğimiz için javascript penceresi bizi ilgilendirmekte.

Main.qml

import QtQuick 2.9
import QtQuick.Controls 2.4
import QtQuick.Window 2.2
import QtQuick.Layouts 1.11

Window {

    id: root
    visible: true
    maximumWidth: 720
    minimumWidth: 720
    maximumHeight: 1920
    minimumHeight: 1920

    Rectangle {

        width: parent.width
        height: parent.height
        border.width: 0
        color: "lightblue"

        ListModel {

            id: busModel
        }

        Component {

            id: busDelegate

            Item {

                id: delegateColumn
                width: root.width
                height: 190

                RowLayout {

                    id: row
                    anchors.fill: parent
                    spacing: 50

                    Text {

                        id: line
                        font.bold: true
                        text: name
                        font.family: "Helvetica"
                        font.pointSize: 18
                        Layout.fillWidth: true
                        Layout.minimumWidth: 100
                        Layout.preferredWidth: 45
                        Layout.maximumWidth: 120
                        wrapMode: Text.WordWrap
                    }

                    Text {

                        text: address
                        font.bold: true
                        font.family: "Helvetica"
                        font.pointSize: 20
                        Layout.fillWidth: true
                        Layout.minimumWidth: 300
                        Layout.preferredWidth: 300
                        Layout.maximumWidth: 300
                        Layout.alignment: Qt.AlignCenter
                        wrapMode: Text.WordWrap
                        horizontalAlignment: Text.AlignHCenter
                        verticalAlignment: Text.AlignVCenter
                    }

                    Text {

                        id: timertext
                        ColorAnimation on color {
                            id: anim
                            to: "red"
                            duration: 300
                        }

                        font.bold: true
                        text: phone
                        font.family: "Helvetica"
                        font.pointSize: 20
                        Layout.alignment: Qt.AlignCenter
                    }
                }

                Line {
                }
            }
        }

        ListView {

            anchors {
                fill: parent
            }
            model: busModel
            delegate: busDelegate
        }
    }

    function request(url, callback) {

        var xhr = new XMLHttpRequest()
        xhr.withCredentials = true

        xhr.onreadystatechange = (function (myxhr) {
            return function () {
                if (myxhr.readyState === 4) {
                    callback(myxhr)
                }
            }
        })(xhr)

        var data = null
        xhr.open("GET", url)
        xhr.setRequestHeader("Content-Type", "application/json")
        xhr.setRequestHeader(
                    "authorization",
                    "apikey yourtoken")
        xhr.send(data)
    }

    Timer {

        id: timer
        interval: 60000
        running: true
        repeat: true
        triggeredOnStart: true
        onTriggered: request(
                         "https://api.collectapi.com/health/dutyPharmacy?ilce=%C3%87ankaya&il=Ankara",
                         function (o) {
                             console.log(o.responseText)
                             if (o.status === 200) {

                                 busModel.clear()

                                 var obj = JSON.parse(o.responseText).result
                                 for (var i = 0; i < obj.length
                                      && i < 12; ++i) {
                                     busModel.append({
                                                         name: obj[i].name,
                                                         address: obj[i].address,
                                                         phone: obj[i].phone
                                                     })
                                 }
                             } else {

                                 console.log(" Connection Failed -> " + o.status)
                             }
                         })
    }
}

Line.qml

import QtQuick 2.0

Rectangle{
    width: parent.width
    height: 1
    border.width: 1
    border.color: "gray"
    color: "gray"
    anchors.horizontalCenter: parent.horizontalCenter
}

Hazırladığımız projeyi Build ettikten sonra hedef sisteme Deploy edebiliriz. Benim oluşturduğum proje adı untitled olarak isimlendirilmişti. Görselde görebileceğiniz gibi, root altına gelmiş durumda. ./untitled diyerek projeyi çalıştırabiliriz.

Raspberry Pi’nin hdmi çıkışı üzerinden kullandığım ekranı bu linkte bulabilirsiniz. Bu ekran ya da başka bir ekran olsun raspberry pi üzerine taktığınızda bir takım ayarlar yapmanız gerekiyor. Ekran çözünürlüğü, şayet ihtiyaç var ise ekran yönünün çevrilmesi gibi. Bu ayarları config.txt içerisinden ayarlayabiliriz. Daha önce bahsettiğim gibi sistem dosyalarını başka bir dizine mount etmemiz gerekiyor öncelikle.

Bu işlem sonrasında , config.txt içerisine girerek kendi ekranımız için şu 2 satırı giriyoruz.

display_rotate=3
hdmi_cvt=1920 720 60 3 0 0 0

Komutlardan anlayabileceğiniz, ekranı döndürme işlemi gerçekleştiriyor ve ekran uzunluk, genişlik, yenileme hızı gibi parametrelerini girmekteyiz. Uygulamayı çalıştırdığımda aldığım ekran görüntüsünü aşağıda görebilirsiniz.

Bu yazıyı hazırlama, bu bilgileri deneyimleme, öğrenme sürecinde başta Metin Koç olmak üzere aşağıda bulunan kaynakların sahiplerine de teşekkür ederim.

17.03.2021 Ekleme :

Ayarladığınız ekran çözünürlüğü enerji kesip verdiğinizde eski haline dönüyor fakat reboot ettiğinizde doğru çalışıyor ise, açılış için ufak bir süre vermeniz gerekebilir. Bunun için config.txt dosyası içerisine aşağıda bulunan satırı ekleyebilirsiniz.

boot_delay_ms=1000

Kaynaklar :

Make your text-based menu in less than 1 minute! Meet with DuruMenu !


While I was studying at university, I had a variety of jobs that required a text-based menu. At that time, I could not find tools to speed up my process. And I had done unmanageable jobs with dirty codes.

In the past month I started to learn Qt, why don’t I make a menu generator, so I learned the widget usage and the basic gui side. I also started by thinking that I would repeat many data structures. Last year, I made a text-based menu with a linked list. (here)

if you wonder how it works, you can check out Duru (here)

A quick demo of the menu generator ;

It creates the necessary structs for you, independently of the platform.

Project Status : Under Development