KitimSende – Üniversite Öğrencileri İçin Ücretsiz Elektronik Kit Desteği Sağlayan Girişim


Uzun süredir amatörce gerçekleştirdiğimiz ve kısa sürede geniş kitlelere ulaşmak için Seckin GULEVIZ ve Berat Yıldız ile birlikte #KitimSende ismini verdiğimiz projemizi yayına aldık.

Gün geçtikçe öğrencilerin kendilerini geliştirebilmesi için ihtiyaç duyduğu fiziksel donanımlara erişiminin ekonomik olarak zorlayıcı olduğunun farkındayız. 

KitimSende, gömülü yazılım alanında çalışan üniversite öğrencilerinin elektronik kitlere ulaşmasını kolaylaştırmak amacıyla kurulmuş bir platformdur. Bu bağlamda kit bağışlamak ve kit sahiplenmek isteyen kişileri bir araya getirmeyi hedeflemektedir. KitimSende, karşılıklı güven ortamını sağlamak amacıyla 
kullanıcların sadece LinkedIn hesabıyla giriş yaptıkları bir platformdur. Platformun akışı 4 ana adımdan oluşmaktadır.

1 – Bağışçılar ve öğrenciler platforma giriş yaparlar.
2 – Bağışcılar ilanlarını oluşturur.
3 – Öğrenciler ihtiyaçları doğrultusunda tercih ettikleri ilan için ‘Kit Mektubu’ alanını doldurarak başvurularını tamamlarlar.
4 – Bağışçı tarafından seçilen öğrenciye kit bağışçı tarafından gönderilir. 

Daha fazla kişinin KitimSende diyebilmesi için etkileşim desteklerinizi bekliyoruz.

Sitede şuanda bağışçılar tarafından bırakılmış çeşitli ürünler bulunmakta, girip incelemek isterseniz

https://kitimsende.com/

*Masaüstünden giriş yapmanızı tavsiye ediyoruz.

RTI CONNEXT DDS


Dökümanın Amacı

Bu dökümanın amacı DriverAlerts_subscriber.cxx dosyası üzerinden bir componentin yazılımını incelemek ve bir yayın yapan modülün yazılma sürecini anlatmak amacıyla hazırlanmıştır. İlgili dosya (DriverAlerts_subscriber.cxx) code base içerisinde bulunan HMI klasörünün altında bulunmaktadır. Bu modülün amacı, Alert topic’e abonelik sağlamak ve kullancıya bir pop-up çıkarmaktır. Bu dökümana geçmeden önce mevcut haliyle sistemin ayağa kaldırıldığından emin olunması gerekmektedir.

Repo : https://github.com/rticommunity/rticonnextdds-usecases-automotive

Bilinmesi Gereken Terminolojiler

  • Domain → Birbiriyle iletişimde bulunan uygulamaları birbirine bağlayan yapı. (Sisteme yeni eklenecek modülün diğer uygulamalar arasında konuşması domainId’nin uyumu ile mümkündür.)
  • Domain Participant Uygulamaların domaine olan üyeliğini temsil eder.
  • Topic → İletişim kurmak için tanımlı alanlar ve ilgili servis kalitelerini içerir.

Bir Modülün Kod Akışı

  1. Öncelikle DomainParticipant nesnesi oluşturulur.
  2. Oluşturulan nesne üzerinden publisher ve subscriber nesneleri hayata getirilir.
  3. Subscriber ve publisher nesneleri üzerinden DataWriter ve DataReader nesneleri hayata getirilir. Bir modül içerisinde birden fazla publisher-subscriber olacağı için böyle bir durumda ilgili her farklı component için DataWriter/DataReader nesneleri oluşturulur.

Yukarda bulunan adımları kod üzerinde (DriverAlerts_subscriber.cxx) görelim;

Line 124

Line 134

Line 172

Sisteme Yeni Bir Modül Ekleyelim

  • Eğer sisteme ekleyeceğiniz modül varolan sistem içerisinde ki herhangi bir topic’e abonelik yapacaksa .idl dosyası içerisine yeni bir type eklemenize ihtiyacınız yok. Ancak bugün modülümüz bir mesaj yayınlacağı için, ilgili mesajın domain içerisinde bulunan diğer modüller tarafından da istenildiği zaman anlaşılması için .idl dosyasına ekleme yapılması gerekmektedir. Bunun için .idl dosyasına yaptığım eklemeyi aşağıda görebilirsiniz.
module Helloworld {
    struct HelloWordMessage {
        string<256> msg;
    };
};

Yukarda bulunan module anahtar kelimesi hakkında detaylı bilgiye şuradan ulaşabilirsiniz. Bu işlemden sonra ilgili type için daha sonra kullanacağımız sınıfların oluşturulması amacıyla idl generator tool’unun tekrar çalıştırılması gerekmektedir. Parametre sisteminize göre değişiklik gösterebilir.

make -f make/Makefile_x64Linux3gcc5.4.0
  • Sisteme yeni modül eklenirken ilgili modülün konfigürasyonlarının daha yönetilebilir ve parametrik hale getirebilmek adına .properties dosyası ekleyebiliriz. Bu kısım aslında opsiyonel bir süreç ancak bu şekilde daha rahat bakımı yapılabilir bir modül elde ediyoruz. Bunun için resource klasörü altına yeni bir dosyayı .properties uzantısı ile ekliyoruz. Bu dosyanın içine hangi topic’e sub olacağımızı hangi topic’e veri göndereceğimizi, hangi sıklıklarla veriyi göndereceğimiz gibi çeşitli parametreleri girebiliyoruz. Ben şu an için aşağıda bulunan parametreleri girdim. (3.adım içerisinde verilmiş XML kodları ile aşağıda bulunan parametrelerini tekrar incelemekte fayda var.)
//hello_world.properties
topic.Hello=Hello
qos.Library=Demo_Library
qos.Hello.Profile=Hello_World_Profile

config.domainId=0
config.pubInterval=1000
  • Bir DDS sistemini XML dosyası üzerinden ilgili tagleri parse ederek oluşturmak mümkün. Bu kısım aslında zorunlu bir kısım değil ancak sistem büyüdükçe, bu sistemin bakımının daha rahat yapılabilmesi gibi özellikler gözönüne alındığında büyük sistemler için mutlaka kullanılması gereken bir yapı. XML dosyasında çeşitli tagler arasına yazacağınız ifadeler ile birlikte modülünüz DomainParticipant, DataWriter, DataReader ismi gibi bir çok parametrenin isimlendirilmesi yapılabilir. Bu tagleri ezberlemek zaten çok mümkün değil, ihtiyaç oldukça öğrenilebilir. Şuradan konu ile ilgili okuma yapabilirsiniz.
<qos_profile name="Hello_World_Profile" base_name="BuiltinQosLibExp::Generic.BestEffort" is_default_qos="false">
      <!-- QoS used to configure the Vehicle Platform data reader and writer created in the example code 
           The data is periodic so it will be sent best effort. A deadline has been added
           to make sure that an error is reported when no data is received for a certian time. -->
      <participant_qos>
        <!--
          The participant name, if it is set, will be displayed in the
          RTI tools, making it easier for you to tell one
          application from another when you're debugging.
        -->
        <participant_name>
          <name>Hello World Platform</name>
        </participant_name>
        <transport_builtin>
          <mask>UDPv4</mask>
        </transport_builtin>        
        <discovery>
          <initial_peers>
            <!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
            <!-- Insert addresses here of machines you want     -->
            <!-- to contact                                     -->
            <!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
            <element>127.0.0.1</element>
            <!-- <element>192.168.1.2</element>-->
          </initial_peers>
        </discovery>
      </participant_qos>
      <datareader_qos>
        <subscription_name>
          <name>Hello World Reader</name>
        </subscription_name>
        <deadline>
          <period>
            <nanosec>0</nanosec>
            <sec>11</sec>
          </period>
        </deadline>
        <history>
          <depth>100</depth>
        </history>
      </datareader_qos>
      <datawriter_qos>
        <publication_name>
          <name>Hello World Publisher</name>
        </publication_name>
        <deadline>
          <period>
            <nanosec>0</nanosec>
            <sec>10</sec>
          </period>
        </deadline>
      </datawriter_qos>
    </qos_profile>
  • Artık sisteme ekleyeceğimiz modül için src dizini altına kendi klasörümüzü oluşturarak, .cpp uzantılı dosyamızı klasörümüz altına ekleyebiliriz. (Benim klasörüm ExternalHW isimli klasör.)
  • Bu kısım önemli bir kısım, eklediğiniz modülün derlenmesi, link edilmesi gibi süreçlerin yapılabilmesi için make dizini altında bulunan Makefile.common klasörü altında düzenlemeler yapılması gerekmektedir.
    • Değişkenler set edilir. (Eklediğiniz dizin ve dosya adına göre değişiklik gösterecektir.)
###############################################################################
# Hello World
###############################################################################

SOURCES_HELLO        = src/ExternalHW/external_hardware.cxx

SOURCES_HELLO_NODIR  = $(notdir $(SOURCES_HELLO))
HELLO_OBJS           = $(SOURCES_HELLO_NODIR:%.cxx=objs/$(ARCH)/%.o)
HELLO_EXE            = objs/$(ARCH)/HelloWorld
  • Build Rules içerisine modülünüz eklenir. (ExternalHW isimli kısımlar yeni eklenen kısımlardır.)
all: $(DIRECTORIES) Vision Collision_Avoidance HMI Lidar CameraImageDataSub CameraImageDataPub Sensor_Fusion Vehicle_Platform ExternalHW

Vision:			$(DIRECTORIES) $(IDL_OBJS) $(DATA_OBJS) \
			$(PROP_OBJS) $(VISION_OBJS)
			$(LINKER) $(LINKER_FLAGS)   -o $(VISION_EXE) $(IDL_OBJS) \
                        $(DATA_OBJS) $(PROP_OBJS) $(VISION_OBJS) $(LIBS)

Collision_Avoidance:	$(DIRECTORIES) $(IDL_OBJS) $(PROP_OBJS) \
			$(CA_OBJS)
			$(LINKER) $(LINKER_FLAGS)   -o $(CA_EXE) $(IDL_OBJS) \
			$(PROP_OBJS) $(CA_OBJS) $(LIBS)

HMI:			$(DIRECTORIES) $(IDL_OBJS) $(PROP_OBJS) \
			$(HMI_OBJS)
			$(LINKER) $(LINKER_FLAGS)   -o $(HMI_EXE) $(IDL_OBJS) \
			$(PROP_OBJS) $(HMI_OBJS) $(LIBS)

Lidar:			$(DIRECTORIES) $(IDL_OBJS) $(PROP_OBJS) \
			$(LIDAR_OBJS)
			$(LINKER) $(LINKER_FLAGS)   -o $(LIDAR_EXE) $(IDL_OBJS) \
			$(PROP_OBJS) $(LIDAR_OBJS) $(LIBS)

CameraImageDataSub: $(DIRECTORIES) $(IDL_OBJS) $(PROP_OBJS) \
			$(CAMDATASUB_OBJS)
			$(LINKER) $(LINKER_FLAGS)   -o $(CAMDATASUB_EXE) $(IDL_OBJS) \
			$(PROP_OBJS) $(CAMDATASUB_OBJS) $(LIBS)

CameraImageDataPub: $(DIRECTORIES) $(IDL_OBJS) $(PROP_OBJS) \
			$(CAMDATAPUB_OBJS)
			$(LINKER) $(LINKER_FLAGS)   -o $(CAMDATAPUB_EXE) $(IDL_OBJS) \
			$(PROP_OBJS) $(CAMDATAPUB_OBJS) $(LIBS)

Sensor_Fusion:		$(DIRECTORIES) $(IDL_OBJS) $(PROP_OBJS) \
			$(SF_OBJS)
			$(LINKER) $(LINKER_FLAGS)   -o $(SF_EXE) $(IDL_OBJS) \
			$(PROP_OBJS) $(SF_OBJS) $(LIBS)

Vehicle_Platform:	$(DIRECTORIES) $(IDL_OBJS) $(DATA_OBJS) \
			$(PROP_OBJS) $(VP_OBJS)
			$(LINKER) $(LINKER_FLAGS)   -o $(VP_EXE) $(IDL_OBJS) \
			$(DATA_OBJS) $(PROP_OBJS) $(VP_OBJS) $(LIBS)

Vehicle_Platform:	$(DIRECTORIES) $(IDL_OBJS) $(DATA_OBJS) \
			$(PROP_OBJS) $(VP_OBJS)
			$(LINKER) $(LINKER_FLAGS)   -o $(VP_EXE) $(IDL_OBJS) \
			$(DATA_OBJS) $(PROP_OBJS) $(VP_OBJS) $(LIBS)

ExternalHW:		$(DIRECTORIES) $(IDL_OBJS) $(PROP_OBJS) \
			$(HELLO_OBJS)
			$(LINKER) $(LINKER_FLAGS)   -o $(HELLO_EXE) $(IDL_OBJS) \
			$(PROP_OBJS) $(HELLO_OBJS) $(LIBS)
  • Son olarak objs klasörü altına çıktının oluşturulması için aşağıda bulunan kısım eklenir.
objs/$(ARCH)/%.o : src/ExternalHW/%.cxx
		$(COMPILER) $(COMPILER_FLAGS)  -o $@ $(DEFINES) $(INCLUDES) -c $<
  • Artık eklediğimiz dosyaya gelerek kodlarımızı yazabiliriz.(Bir Modülün Akışı kısmını kafada oturmakta fayda var.) Kodun akışı ile ilgili anlaşılmayan yer var ise birlikte bakabiliriz.)
#include "Utils.h" // Dosya işlemleri
#include "automotive.h" //IDL üzerinden generate edilen ortak arayüz ile ilgili
#include "automotiveSupport.h" //IDL üzerinden generate edilen ortak arayüz ile ilgili
#include "ndds/ndds_cpp.h" //Standart kütüphanelerimiz

static int shutdown(
    DDSDomainParticipant *participant)
{
    DDS_ReturnCode_t retcode;
    int status = 0;

    if (participant != NULL) {
        retcode = participant->delete_contained_entities();
        if (retcode != DDS_RETCODE_OK) {
            printf("delete_contained_entities error %d\n", retcode);
            status = -1;
        }

        retcode = DDSTheParticipantFactory->delete_participant(participant);
        if (retcode != DDS_RETCODE_OK) {
            printf("delete_participant error %d\n", retcode);
            status = -1;
        }
    }

    return status;
}

extern "C" int publisher_main(int sample_count)
{
    DDSDomainParticipant *participant = NULL;
    DDSPublisher *publisher = NULL;
    DDSTopic *topic = NULL;
    DDSDataWriter *writer = NULL;

    Helloworld_HelloWordMessageDataWriter* Hello_WorldMessage_writer = NULL;
    Helloworld_HelloWordMessage* helloworld_instance = NULL;
    
    DDS_InstanceHandle_t instance_handle = DDS_HANDLE_NIL;
    int domainId = 0;
    const char *type_name = NULL;
    DDS_ReturnCode_t retcode;
    DDS_Duration_t send_period = {20,0};


    PropertyUtil* prop = new PropertyUtil("hello_world.properties");
    domainId = prop -> getLongProperty("config.domainId");
    
    long time = prop->getLongProperty("config.pubInterval");
    send_period.sec = time / 1000;
    send_period.nanosec = (time % 1000) * 1000 * 1000;

    std::string platformTopicName = prop->getStringProperty("topic.Hello");
    if (platformTopicName == "") {
        printf("No platform topic name specified\n");
        return -1;
    }
    //Qos Parametrelerinin Olduğu Dosya Adi Alinir.
    std::string qosLibrary = prop->getStringProperty("qos.Library");
    if (qosLibrary == "") {
        printf("No QoS Library specified\n");
        return -1;
    }

    //İlgili Component İçin Gerekli kütüphane detaylari alinir.
    std::string HelloWorldQosProfile = prop->getStringProperty("qos.Hello.Profile");
    if (HelloWorldQosProfile == "") {
        printf("No Planning QoS Profile specified\n");
        return -1;
    }

    //Participant oluşturuldu.
    participant = DDSTheParticipantFactory->create_participant_with_profile(
        domainId, qosLibrary.c_str(), HelloWorldQosProfile.c_str(),
        NULL /* listener */, DDS_STATUS_MASK_NONE);
    if (participant == NULL) {
        printf("create_participant error\n");
        shutdown(participant);
        return -1;
    }

    //Publisher oluşturuldu
    publisher = participant->create_publisher_with_profile(
        qosLibrary.c_str(), HelloWorldQosProfile.c_str(), NULL /* listener */, DDS_STATUS_MASK_NONE);
    if (publisher == NULL) {
        printf("create_publisher error\n");
        shutdown(participant);
        return -1;
    }

    type_name = Helloworld_HelloWordMessageTypeSupport::get_type_name();
    retcode = Helloworld_HelloWordMessageTypeSupport::register_type(
        participant, type_name);
    if (retcode != DDS_RETCODE_OK) {
        printf("register_type error %d\n", retcode);
        shutdown(participant);
        return -1;
    }

    topic = participant->create_topic_with_profile(
        platformTopicName.c_str(),
        type_name, qosLibrary.c_str(), HelloWorldQosProfile.c_str(), NULL /* listener */,
        DDS_STATUS_MASK_NONE);
    if (topic == NULL) {
        printf("create_topic error\n");
        shutdown(participant);
        return -1;
    }

    writer = publisher->create_datawriter_with_profile(
        topic, qosLibrary.c_str(), HelloWorldQosProfile.c_str(), NULL /* listener */,
        DDS_STATUS_MASK_NONE);
    if (writer == NULL) {
        printf("create_datawriter error\n");
        shutdown(participant);
        return -1;
    }
	//Neden bunu yapıyoruz diye merak ederseniz.
	//https://community.rti.com/kb/what-purpose-narrow-method-created-type-rtiddsgen
    Hello_WorldMessage_writer = Helloworld_HelloWordMessageDataWriter::narrow(writer);
    if (Hello_WorldMessage_writer == NULL) {
        printf("DataWriter narrow error\n");
        shutdown(participant);
        return -1;
    }

    helloworld_instance = Helloworld_HelloWordMessageTypeSupport::create_data();
    if (helloworld_instance == NULL) {
        printf("Platform_PlatformStatusTypeSupport::create_data error\n");
        shutdown(participant);
        return -1;
    }

    for (int count=0; (sample_count == 0) || (count < sample_count); ++count) {

        printf("\r\n Hello World Message Sended");
        
        if (count % 2 == 0) {
            helloworld_instance->msg = (char*)"Test";
        }

        else {
            helloworld_instance->msg = (char*)"Hello World";
        }
      
        retcode = Hello_WorldMessage_writer->write(*helloworld_instance, instance_handle);
        if (retcode != DDS_RETCODE_OK) {
            printf("write error %d\n", retcode);
        }

        NDDSUtility::sleep(send_period);
    }

    retcode = Helloworld_HelloWordMessageTypeSupport::delete_data(helloworld_instance);
    if (retcode != DDS_RETCODE_OK) {
        printf("Helloworld_HelloWordMessageTypeSupport::delete_data error %d\n", retcode);
    }

    /* Delete all entities */
    return shutdown(participant);
}

int main(int argc, char *argv[])
{
    int sample_count = 0;

    if (argc >= 2) {
        sample_count = atoi(argv[1]);
    }
    
    return publisher_main(sample_count);
}

Modülümüzü RTI Admin Konsolu üzerinden inceleyelim.

  • Yapılan tüm değişiklikler sonrasında programı derleyip çalıştırın ve RTI kurulumu ile birlikte Launcher üzerinden Admin Console çalıştırın.

Sistemimiz içerisine Hello isimli bir modül gelmiş bulunmakta. Sağ click yaparak ilgili modüle abonelik sağlayalım. Fotoğraflarda görüldüğü gibi msg içeriğinin yukarda yazdığımız count değişkeninin tek ya da çift olma durumuna göre değiştiğini görebiliriz.

Eklediğimiz Bir Modülün Yaptığı Yayına Başka Bir Modül Abone Olalım.

Az önce geliştirdiğimiz modülü RTI’n bize sağlamış olduğu bir tool üzerinden inceleyebildik. Bu bölümde ise yayına abone olalım ve dataları ekrana basalım. Bunun için HMI Modülünü seçtim ve HMI modülü üzerinden Hello ismini verdiğimiz topic’e abone olacağız.

  • Öncelikle hmi.properties dosyasında çeşitli değişikler yaparak, abone olacağımız topic ismi gibi detayları ekleyeceğim. Eklenen değişiklikler aşağıda verilmiştir.
topic.Hello=Hello
qos.Hello.Profile=Hello_World_Profile
  • DriverAlerts_subscriber.cxx dosyasına DDSDataReaderListener isimli classtan türetilerek yeni bir listener sınıfı yazılır.(Bir Modülün Akışı kısmını kafada oturmakta fayda var.). Aşağıda verilen kodlarda /// <yapılan değişikler> /// şeklinde sonradan eklenen kısımlar görülebilir
/****************************************************************************
(c) 2005-2017 Copyright, Real-Time Innovations, Inc.  All rights reserved.                                     
RTI grants Licensee a license to use, modify, compile, and create derivative 
works of the Software.  Licensee has the right to distribute object form 
only for use with RTI products.  The Software is provided 'as is', with no
arranty of any type, including any warranty for fitness for any purpose. RTI
is under no obligation to maintain or support the Software.  RTI shall not
be liable for any incidental or consequential damages arising out of the 
use or inability to use the software.
*****************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include "Utils.h"

#include "automotive.h"
#include "automotiveSupport.h"
#include "ndds/ndds_cpp.h"

////////////////////////////////////////////////////////////////////////////////
class Hello_HelloWorldListenerClass : public DDSDataReaderListener {
    public:
    virtual void on_requested_deadline_missed(
        DDSDataReader* /*reader*/,
        const DDS_RequestedDeadlineMissedStatus& /*status*/) {
        printf("on_requested_deadline_missed\n");
    }

    virtual void on_requested_incompatible_qos(
        DDSDataReader* /*reader*/,
        const DDS_RequestedIncompatibleQosStatus& /*status*/) {
        printf("on_requested_incompatible_qos\n");
    }

    virtual void on_sample_rejected(
        DDSDataReader* /*reader*/,
        const DDS_SampleRejectedStatus& /*status*/) {
        printf("on_sample_rejected\n");
    }

    virtual void on_liveliness_changed(
        DDSDataReader* /*reader*/,
        const DDS_LivelinessChangedStatus& /*status*/) {
        printf("on_liveliness_changed\n");
    }

    virtual void on_sample_lost(
        DDSDataReader* /*reader*/,
        const DDS_SampleLostStatus& /*status*/) {
        printf("on_sample_lost\n");
    }

    virtual void on_subscription_matched(
        DDSDataReader* /*reader*/,
        const DDS_SubscriptionMatchedStatus& /*status*/) {
        printf("on_subscription_matched\n");
    }

    virtual void on_data_available(DDSDataReader* reader);
};

void Hello_HelloWorldListenerClass::on_data_available(DDSDataReader* reader)
{
    Helloworld_HelloWordMessageDataReader *HelloWorldMessage_reader = NULL;
    Helloworld_HelloWordMessageSeq data_seq;
    DDS_SampleInfoSeq info_seq;
    DDS_ReturnCode_t retcode;
    int i;

    HelloWorldMessage_reader = Helloworld_HelloWordMessageDataReader::narrow(reader);
    if (HelloWorldMessage_reader == NULL) {
        printf("DataReader narrow error\n");
        return;
    }

    /* Read all samples */
    retcode = HelloWorldMessage_reader->take(
        data_seq, info_seq, DDS_LENGTH_UNLIMITED,
        DDS_ANY_SAMPLE_STATE, DDS_ANY_VIEW_STATE, DDS_ANY_INSTANCE_STATE);

    if (retcode == DDS_RETCODE_NO_DATA) {
        return;
    }
    else if (retcode != DDS_RETCODE_OK) {
        printf("take error %d\n", retcode);
        return;
    }

    for (i = 0; i < data_seq.length(); ++i) {
        
        if (info_seq[i].valid_data) {
            /* Print the data and store some data for use in the status message*/
            Helloworld_HelloWordMessageTypeSupport::print_data(&data_seq[i]);
            
        }
    }

    retcode = HelloWorldMessage_reader->return_loan(data_seq, info_seq);
    if (retcode != DDS_RETCODE_OK) {
        printf("return loan error %d\n", retcode);
    }
}
////////////////////////////////////////////////////////////////////////////////

class Alerts_DriverAlertsListener : public DDSDataReaderListener {
  public:
    virtual void on_requested_deadline_missed(
        DDSDataReader* /*reader*/,
        const DDS_RequestedDeadlineMissedStatus& /*status*/) {
        printf("on_requested_deadline_missed\n");
    }

    virtual void on_requested_incompatible_qos(
        DDSDataReader* /*reader*/,
        const DDS_RequestedIncompatibleQosStatus& /*status*/) {
        printf("on_requested_incompatible_qos\n");
    }

    virtual void on_sample_rejected(
        DDSDataReader* /*reader*/,
        const DDS_SampleRejectedStatus& /*status*/) {
        printf("on_sample_rejected\n");
    }

    virtual void on_liveliness_changed(
        DDSDataReader* /*reader*/,
        const DDS_LivelinessChangedStatus& /*status*/) {
        printf("on_liveliness_changed\n");
    }

    virtual void on_sample_lost(
        DDSDataReader* /*reader*/,
        const DDS_SampleLostStatus& /*status*/) {
        printf("on_sample_lost\n");
    }

    virtual void on_subscription_matched(
        DDSDataReader* /*reader*/,
        const DDS_SubscriptionMatchedStatus& /*status*/) {
        printf("on_subscription_matched\n");
    }

    virtual void on_data_available(DDSDataReader* reader) {}
};


/* Delete all entities */
static int subscriber_shutdown(
    DDSDomainParticipant *participant)
{
    DDS_ReturnCode_t retcode;
    int status = 0;

    if (participant != NULL) {
        retcode = participant->delete_contained_entities();
        if (retcode != DDS_RETCODE_OK) {
            printf("delete_contained_entities error %d\n", retcode);
            status = -1;
        }

        retcode = DDSTheParticipantFactory->delete_participant(participant);
        if (retcode != DDS_RETCODE_OK) {
            printf("delete_participant error %d\n", retcode);
            status = -1;
        }
    }

    return status;
}


extern "C" int subscriber_main(int sample_count)
{
    DDSDomainParticipant *participant = NULL;
    DDSSubscriber *subscriber = NULL;
    DDSTopic *topic = NULL;
    Alerts_DriverAlertsListener *reader_listener = NULL;     
    DDSDataReader *reader = NULL;
    DDS_ReturnCode_t retcode;
    const char *type_name = NULL;
    int count = 0;
    int status = 0;
    int domainId = 0;
    DDSWaitSet *waitset = NULL;
    Alerts_DriverAlertsDataReader *Alerts_DriverAlerts_reader = NULL;
    DDS_Duration_t timeout = { 10, 0 };

    /////////////////////////////////////////////////
    Hello_HelloWorldListenerClass* hello_reader_listener = NULL;
    /////////////////////////////////////////////////

    /* Read the properties and configure */
    PropertyUtil* prop = new PropertyUtil("hmi.properties");
    domainId = prop->getLongProperty("config.domainId");
    
    std::string topicName = prop->getStringProperty("topic.Alerts");
    if (topicName == "") {
        printf("No topic name specified\n");
        return -1;
    }

    std::string HelloTopicName = prop->getStringProperty("topic.Hello");
    if (HelloTopicName == "") {
        printf("No planning topic name specified\n");
        return -1;
    }

    std::string HelloQosProfile = prop->getStringProperty("qos.Hello.Profile");
    if (HelloQosProfile == "") {
        printf("No Planning QoS Profile specified\n");
        return -1;
    }

    std::string qosLibrary = prop->getStringProperty("qos.Library");
    if (qosLibrary == "") {
        printf("No QoS Library specified\n");
        return -1;
    }


    std::string qosProfile = prop->getStringProperty("qos.Profile");
    if (qosProfile == "") {
        printf("No QoS Profile specified\n");
        return -1;
    }

    /* Create the participant */
    participant = DDSTheParticipantFactory->create_participant_with_profile(
        domainId, qosLibrary.c_str(), qosProfile.c_str(),
        NULL /* listener */, DDS_STATUS_MASK_NONE);
    if (participant == NULL) {
        printf("create_participant error\n");
        subscriber_shutdown(participant);
        return -1;
    }

    /* Create the subscriber */
    subscriber = participant->create_subscriber_with_profile(
        qosLibrary.c_str(), qosProfile.c_str(), NULL /* listener */, DDS_STATUS_MASK_NONE);
    if (subscriber == NULL) {
        printf("create_subscriber error\n");
        subscriber_shutdown(participant);
        return -1;
    }

    /* Register the type before creating the topic */
    type_name = Alerts_DriverAlertsTypeSupport::get_type_name();
    retcode = Alerts_DriverAlertsTypeSupport::register_type(
        participant, type_name);
    if (retcode != DDS_RETCODE_OK) {
        printf("register_type error %d\n", retcode);
        subscriber_shutdown(participant);
        return -1;
    }

    /* Create the alert topic */
    topic = participant->create_topic_with_profile(
        topicName.c_str(),
        type_name, qosLibrary.c_str(), qosProfile.c_str(), NULL /* listener */,
        DDS_STATUS_MASK_NONE);
    if (topic == NULL) {
        printf("create_topic error\n");
        subscriber_shutdown(participant);
        return -1;
    }

    /* Create a data reader listener */
    reader_listener = new Alerts_DriverAlertsListener();

    /* The listener is used for any events other than on data
       available. Since the alerts will pop up a message box
       it is handled as waitset in the main loop using on data 
       available listener would block the receive which we 
       don't want 
     */
    reader = subscriber->create_datareader_with_profile(
        topic, qosLibrary.c_str(), qosProfile.c_str(), reader_listener,
        DDS_STATUS_MASK_ALL & ~DDS_DATA_AVAILABLE_STATUS);
    if (reader == NULL) {
        printf("create_datareader error\n");
        subscriber_shutdown(participant);
        delete reader_listener;
        return -1;
    }

    /* Create status condition
    * ---------------------
    */
    DDSStatusCondition* status_condition = reader->get_statuscondition();
    if (status_condition == NULL) {
        printf("get_statuscondition error\n");
        subscriber_shutdown(participant);
        return -1;
    }
    /* All we are interessted is the on data available*/
    retcode = status_condition->set_enabled_statuses(
        DDS_DATA_AVAILABLE_STATUS);
    if (retcode != DDS_RETCODE_OK) {
        printf("set_enabled_statuses error\n");
        subscriber_shutdown(participant);
        return -1;
    }


    /* Attach condition to waitset
    * ---------------------------
    */
    waitset = new DDSWaitSet();
    if (waitset == NULL) {
        printf("waitset error\n");
        subscriber_shutdown(participant);
        return -1;
    }

    /* attach status condition to waitset */
    retcode = waitset->attach_condition(status_condition);
    if (retcode != DDS_RETCODE_OK) {
        printf("attach_condition error\n");
        subscriber_shutdown(participant);
        delete waitset;
        return -1;
    }

    /* Narrow data reader to specific type */
    Alerts_DriverAlerts_reader = Alerts_DriverAlertsDataReader::narrow(reader);
    if (Alerts_DriverAlerts_reader == NULL) {
        printf("DataReader narrow error\n");
        subscriber_shutdown(participant);
        delete waitset;
        return -1;
    }

/////////////////////////////////////////////////////////////////////////////////////////////////////

    type_name = Helloworld_HelloWordMessageTypeSupport::get_type_name();
    retcode = Helloworld_HelloWordMessageTypeSupport::register_type(
        participant, type_name);
    if (retcode != DDS_RETCODE_OK) {
        printf("register_type error %d\n", retcode);
        subscriber_shutdown(participant);
        return -1;
    }


    topic = participant->create_topic_with_profile(
        HelloTopicName.c_str(),
        type_name, qosLibrary.c_str(), HelloQosProfile.c_str(), NULL /* listener */,
        DDS_STATUS_MASK_NONE);
    if (topic == NULL) {
        printf("create_topic error\n");
        subscriber_shutdown(participant);
        return -1;
    }

    hello_reader_listener = new Hello_HelloWorldListenerClass();

    reader = subscriber->create_datareader_with_profile(
        topic, qosLibrary.c_str(), HelloQosProfile.c_str(), hello_reader_listener,
        DDS_STATUS_MASK_ALL);
    if (reader == NULL) {
        printf("create_datareader error\n");
        subscriber_shutdown(participant);
        return -1;
    }
    ///////////////////////////////////////////////////////////////////////////////////////


    /* Main loop */
    for (count=0; (sample_count == 0) || (count < sample_count); ++count) {

        Alerts_DriverAlertsSeq data_seq;
        DDS_SampleInfoSeq info_seq;
        DDSConditionSeq active_conditions_seq;

        /* wait() blocks executione until condition becomes ture or timeout */
        retcode = waitset->wait(active_conditions_seq, timeout);

        if (retcode == DDS_RETCODE_TIMEOUT) {
            continue;
        }
        else if (retcode != DDS_RETCODE_OK) {
            printf("wait returned error: %d", retcode);
            break;
        }
        /* Check what caused the wait to return. It can really
           on be the on data available for the alert topic
         */
        int active_conditions = active_conditions_seq.length();

        for (int i = 0; i < active_conditions; i++) {
            if (active_conditions_seq[i] == status_condition) {
                /* Take the data */
                retcode = Alerts_DriverAlerts_reader->take(
                    data_seq, info_seq, DDS_LENGTH_UNLIMITED,
                    DDS_ANY_SAMPLE_STATE, DDS_ANY_VIEW_STATE, DDS_ANY_INSTANCE_STATE);

                if (retcode == DDS_RETCODE_NO_DATA) {
                    continue;
                }
                else if (retcode != DDS_RETCODE_OK) {
                    printf("take error %d\n", retcode);
                }

                /* If we have valid data process it */
                for (i = 0; i < data_seq.length(); ++i) {
                    if (info_seq[i].valid_data) {
                        /* Print the sample for information purpose*/
                        Alerts_DriverAlertsTypeSupport::print_data(&data_seq[i]);

                        /* Pop-up the right message box. On Linux we use SDL2 */
                        if (data_seq[i].backCollision) {
                            MessageBoxUtil::PopUp((char *)"Back Collision Warning", MSGBOX_WARNING);
                        }
                        if (data_seq[i].blindSpotDriver) {
                            MessageBoxUtil::PopUp((char *)"Car in blind spot on driver side", MSGBOX_INFO);
                        }
                        if (data_seq[i].blindSpotPassenger) {
                            MessageBoxUtil::PopUp((char *)"Car in blind spot on passanger side", MSGBOX_INFO);
                        }
                        if (data_seq[i].driverAttention) {
                            MessageBoxUtil::PopUp((char *)"Driver Attention", MSGBOX_ATTENTION);
                        }
                        if (data_seq[i].frontCollision) {
                            MessageBoxUtil::PopUp((char *)"Front Collision Warning", MSGBOX_ATTENTION);
                        }
                        if (data_seq[i].parkingCollision) {
                            MessageBoxUtil::PopUp((char *)"Parking Collision Warning", MSGBOX_WARNING);
                        }
                    }
                }
            }

            retcode = Alerts_DriverAlerts_reader->return_loan(data_seq, info_seq);
            if (retcode != DDS_RETCODE_OK) {
                printf("return loan error %d\n", retcode);
            }

        }
    }

    /* Delete all entities */
    status = subscriber_shutdown(participant);
    delete reader_listener;
    delete waitset;
    return status;
}

int main(int argc, char *argv[])
{
    int sample_count = 0; /* infinite loop */


    if (argc >= 2) {
        sample_count = atoi(argv[1]);
    }

    /* Uncomment this to turn on additional logging
    NDDSConfigLogger::get_instance()->
    set_verbosity_by_category(NDDS_CONFIG_LOG_CATEGORY_API, 
    NDDS_CONFIG_LOG_VERBOSITY_STATUS_ALL);
    */

    return subscriber_main(sample_count);
}

Yapılan değişikliklerden sonra objs klasörü tamamen silinerek, tekrar derleme işlemi yapılması gerekmektedir. Sistemi çalıştırdığımızda HMI terminalimizde Alarm oluşmadan önce verilerin geldiği görülebilir.

Alarm oluştuğunda ise alarm oluştuğuna dair veri ve abone olduğumuz Hello topic’e ait veriler gözlenebilir.

RTI Launcher üzerinden incelendiğinde HMI modülünün artık 2 farklı topic’e abone olduğu görülebilir.

Buraya kadar bizimle olduğun için teşekkür ederim 🙂

Bağlı Listeler, Manzara, Tımer, Onur Saylak


Veri yapılarına yönelik içeriklerin bir çoğu herhangi bir kullanım şekli (use-case) senaryosuna değinmek yerine sadece ne olduğuna dair genel bir bilgi veriyor. Bu tarz öğrenme şekli benimle çok uyuşmadığı ve öğrenme metodu benim gibi olan bireylerin varlığı için temel veri yapılarından olan bağlı listeyi (linked list) kendimce bir tane kullanım senaryosuna yedirerek bir yazı yazmak istedim.

Yukarda bulunan görsel üzerinden bağlı liste için genel terminolojiyi konuşabiliriz.

  1. Hepsi kendi başına bir birey (node <-> düğüm)
  2. En solda bulunan arkadaş baş düğümümüz (head, root node),
  3. En sağda duran ve halinden çok memnun gözükmeyen arkadaşımız ise son düğümümüz (tail node).
  4. Bu 4 arkadaşın bir araya gelmesi ile oluşan yapıyı ise bağlı liste (linked list) olarak adlandırabiliriz. Daha spesifik isimlendirecek olursak bu tek yönlü bir bağlı listedir(single linked list). (Çift taraflı bağlı listenin (double linked list) bu görsele göre nasıl olacağını bir canlandırabilirsiniz.)

Gelelim bir kullanım senaryosuna, donanımın bize sağladığı bir timer mekanizmasını kullanarak kendi yazılımsal timer modülümüzü yazmaya çalışacağız. Parkura çıktığımızı ve kronometremizi tetiklediğimizi düşünelim(System tick olarak düşünebiliriz.) Yaldır yaldır koşmaya başladık (askeri lise mülakatında öyle koşmuştum, tam tamına 400 metre) bu sürecinin sonunda neyi hedeflediğimizi bir çıkaralım.

  1. Bitirmek istediğimiz bir süre olması gerekiyor, [Interval]
  2. Hedeflediğimiz sürede parkuru bitirdiğimizde devam mı edeceğiz yoksa tamam kardeşim diyerek yere mi yığılıyoruz [Single Shot or Auto Reloaded Timer]
  3. Sürenin sonunda almamız gereken bir aksiyon var mıdır ? Bir su içelim ? [Callback when the timer expired]
  4. Koşunun ortasında bakmak isteyeceğimiz diğer bir olay ise kalan zaman. [remaining time]
  5. Koşuya ne zaman başladığımız [starting timestamp]

Koda dökmek istersek;

typedef enum timer_type
{
  SHOT,
  LOOP
}timer_type_e;

typedef struct timer_s
{   
  struct timer_s* timer_list; ///< Next entry in the single linked list
  timer_type_e type; ///< Single Shot or auto - reloaled
  void *arg; ///< Placeholder for argument will pass when the timer is expired.
  void (*exp_func)(void *); ///< Call the callback function When the timer is expired
  ut32_timer remaining_time_to_expire; ///< Remaining time to the fire!
  ut32_timer starting_timestamp; ///<Placeholder for starting timestamp
  ut32_timer interval; ///< milisecond
}timer_t;

Parkurda koşmaya devam ediyoruz ve sen yaparsın diyen çocuğun(bkz) babasıda koşmaya başladı. Koşmaya yeni başlayan bireyi tutmak ve halihazırda koşan birisine bağlamak için kullandığımız nokta ise tam olarak şurası.

struct timer_s* timer_list;

İlk koşmaya başlayan kişiyi referans alarak diğer koşan bireyleri de tutmaya başladık. Peki ilk koşan kişiyi kim tutacak ? Hadi gelin;

typedef struct timer_mngmnt_s
{
  timer_t* root; ///< Handle root
  ut8_timer count_of_active_timers; ///< Total number of the running timers;
}timer_mngmnt_t;

static timer_mngmnt_t tmr_mngmnt = {.root = NULL, .count_of_active_timers = 0};

Şimdi parkur başına bir tane koşucuyu alalım ve yukarda bahsettiğimiz hedeflerini bize bir söylesin.

void init_timer(timer_t *tmr, timer_type_e type, void *arg, void(*exp_func)(void*), ut32_timer interval)
{
  tmr -> timer_list = NULL;
  tmr -> type = type;
  tmr -> exp_func = exp_func;
  tmr -> arg = arg;
  tmr -> remaining_time_to_expire = 0;
  tmr -> interval = interval;
  tmr -> starting_timestamp = 0;
}

Koşmaya başlasın.

static void timer_add(timer_t *tmr)
{
  if (NULL == tmr_mngmnt.root)
  {
    tmr_mngmnt.root = tmr;
  }
  else 
  {
    timer_t *pos = tmr_mngmnt.root;
    
    while (NULL != pos -> timer_list)
    {      
      pos = pos -> timer_list;
    }
    
    pos -> timer_list = tmr;
  }

  ++tmr_mngmnt.count_of_active_timers;
}

Eğer parkur boş ise koşan ilk kişiyi baş düğüm olarak belirledik. Şayet halihazırda parkurda koşan diğer kişiler var ise parkura ilk çıkan kişi üzerinden en son çıkanı bulduk ve onaşiii (onunla ilişkilendirdik yazmak istedim ama kedi klavyeye bastı ve anı kalması adına silmiyorum.)

Tabi koşmaya başlayan bireyler hayatlarının sonlarına kadar koşmayacaklar ve burada iki senaryo mevcut.

  1. Kişi istediği zaman koşmayı bırakabilir.
  2. Kişi zaten parkuru tek sefer koşmak için gelmiştir.

Şimdi şayet bu kişiyle ilişkilendirilmiş bir kişi var ise bu adam parkurdan ayrılırsa ben onu sonra nereden bulacağım ? O yüzden bu kişi parkurdan ayrılmadan yapmam gerekenler var.

Diyelim parkurda 3 kişi var. (1 -> 2 -> 3) Ve 2. arkadaşımız ayrılmak istiyor. Bu durumda 1 -> 3 arasında ilişkiyi kurmalıyım. Nasıl mı ?

static void unlink_timer(timer_t *tmr)
{
  //Check timer is root ?
  //Ex: root (t1) -> t2 -> t3
  // (t1) == (unlinked_timer)
  // root -> t2 -> t3
  if (tmr_mngmnt.root == tmr)
  {
    tmr_mngmnt.root = tmr -> timer_list;
    --tmr_mngmnt.count_of_active_timers;
    return;
  }

  timer_t *prev = tmr_mngmnt.root;

  for (timer_t* pos = tmr_mngmnt.root; NULL != pos; pos = pos -> timer_list)
  {
    // Ex; root (t1) -> t2 -> t3
    // (t1 -- t2) == unlinked_timer
    // t1 -> t3
    if (prev -> timer_list == tmr)
    {
      prev -> timer_list = tmr -> timer_list;
      --tmr_mngmnt.count_of_active_timers;
      break;
    }    
    //Update to previous one
    prev = pos;
  }  
}

Koşan bireylerin parkura çıkmadan önce belirledikleri hedefleri bir göz gezdirelim.(linked list traverse) Ve bu hedefler doğrultusunda aksiyonlar alalım.

void timer_pool(void)
{
  for (timer_t* pos = tmr_mngmnt.root; NULL != pos; pos = pos -> timer_list)
  {
    if ((get_systick() - pos -> starting_timestamp) >= pos -> interval)
    {
      /* Call expire func */
      if (pos -> exp_func != NULL)
      {
        pos -> exp_func(pos -> arg);
      }

      if (pos -> type == SHOT)
      {
        unlink_timer(pos);
      }
      /* Reload the timer */
      else if (pos -> type == LOOP)
      {
        pos -> starting_timestamp = get_systick();
      }
    }
  }
}

Projenin Atmega328p üzerinde örnek çalışmasını görmek için github reposuna gelebilirsiniz.

Bu yazının ilhamı olan manzarama teşekkür ederek yazıyı sonlandırmak isterim.

Durmuş Kapı Kapanmıyor,Hay Aksi !


Not : Bu yazıyı 5 yıl önce yazmışım, biriken taslakları temizlerken gördüm. Yayına alayım dedim 🙂

Bir okul düşün  Türkiye’nin 5. büyük kampüsünde, Çukurova Üniversitesindesindesin. Tırlar GELİYOR ,sarı , samimi , kendini ait hissettiğin tırlar ; 5 girişimci dünyayı değiştirecekmiş. Ya nazım usta birer kebap uzat arkadaşlara , acılı bir de şalgam ver , neymiş dertleri bu 5 girişimcinin öğrenelim. Tırlar  KURULUYOR  o da ne 3 Boyutlu yazıcı mı lan  o ? , ellemeyelim abi bozulur diyorsun içinden(Ama durur musun hiç ). Tanımıyorsun ki dünyayı değiştircekler onu biliyorsun. İlk geceden izin istiyorsun ve biliyorsunki içerde montajı yapılmamış bir tane daha var, onu yapayım en azından bozamam diyorsun, içinden tabi. Yapıyorsun az çok ama gözün çalışanda. Şu neymiş bu neymiş derken onu sök bunu değiştir o da ne çalışmıyor ! . Oğlum ne yaptın adamlar dünyayı değiştircekler ! sen yazıcılarını bozuyorsun diyorsun tabi yine içinden. Bozduğun gibi tekrar çalıştıyorsun fakat yine bozuyorsun bu sefer yazılımıyla oynamışsın aha ! Neyse ki Stijn geliyor , öğretiyor. Envai çeşit eğitimler alıyorsun, yatırımcı sunumundan tutta ,arduino eğitimine kadar. O Türkiye’nin 5.büyük kampüsü var ya  3 tane Sarı Konteynıra sığıyor ! Şaşırıyorsun, zaman geçiyor tabi. Tırlar TOPLANIYOR bir başka üniversiteyi konteynıra sığdırmaya gidiyor. Bir bakıyorsun Şile’deler. Haydi diyip sende gidiyor, kuruluyorsun oraya. Hala aynı sarı, samimi, müthiş insanlar. Sunumda  Memet abi , 5 girişimci dünyayı değiştirebilir mi diyor ! , sonra Mustafa abi herkesin içindeki dünyadan bahsediyor. Değiştiriyorsunuz ulan işte diye haykırmak istiyorsunuz. Değiştirir diyorsunuz. Eğitim sisteminin ortasındaki koca çatlağın merhemi olan bir oluşum diyorsun. Liseliler gelecekmiş 1 hafta daha kalayım en iyisi diyorsun. Nerden bilebilirsin onlar giderken ağlayacaksın, nerden bileceksin hepsi sana bir şeyler katmış. Siz benim neler çektiğimi ner.. şaka şaka 🙂 ,

Durmuş kapı kapanmıyor abi Adana’da gayette rahat kapanıyordu , “aşağı doğru çek aşağı , sonra hafiften kendine doğru çek”  .

-Çikırt

-Kapandı abi hadi gidelim , sabah Teknoparka gideceğin erken uyanmak lazım.

Farkında değilsin zaten saat sabah olmuş , Böyle bir yer işte InnoCampus.

Çok yaşayın siz dünyayı değiştirmek isteyen insanlar..

Hesap Makineleri Bizi Nasıl Görür ? [Fetişist Makineler]


Kentsel dönüşüme kafa tutan, ne döndüğünü tam kestiremediğimiz bir mahallenin çıkmaz sokağında kaçak katlı bir yapının kapısı kilit tutmayan zemin katında kendinden 2+1 olup, 3+1’e çevirdiğimiz bahçeli bir ev. Sıkça aksattığımız kira, eve geldiğimizde patileri ıslanmış kedimiz Pablo, çoğu zaman çarşafsız bir şekilde duran yer yatağım. Sadece bende kişneyen ve çıplak ayakla gezebileceğim odam. Ve tabi iki tane hayat arkadaşım skolyozlu Süleyman ve sadece konuşursa Maraşlı olduğuna ikna olabildiğim Can. Saatlerce, sıklıkla sabahlara kadar e-dilencilikle aldığım modülleri, geliştirme kartlarını çalışmak için kullandığım plastik düğün sandalyesi ve masası. Düşününce öğrencilik dönemine ait en temiz özeti burası verdi galiba. Uzatmalarını oynadığım okulun son döneminde Ozan ile kaldığımız bir evde var tabi. Ama apartman orası. Ama saatlerce Ozan’ın potansiyelimi hissetmem için benimle konuştuğu bir daireyi içerisinde barındıran apartman. Alt paragrafı yazarken aklıma gelen InnoCampus ortamıda listede zirveye aday.

Pablo

Yağ ağbi ne kıymetli malın varmış a****, dursun bizde bu ver sen bana ile başlayan bir diyalog sonrasında sahibi olduğunu hissettiğim Bim’de satışı yapılan Corby marka döner kanatlı insansız hava aracı(!?). İş eğitimi dersinin hocası tarafından aranan atölyenin paspas çocuğu durur mu, olum adamlar nasıl da yapmış, hangi işlemciyi kullanmışlar, hangi wifi çipini kullanmışlar derken parçalanmış bir insansız hava aracına (geri toplanamayan) dönüşüyor. Parçalarken ulan ufacık kamera kartı, ufacık işlemci ağbi nasıl yapıyorlar acaba diye sorguladığım bir kart. Sonrasında ana devreye bağlantısını izleyerek + ve – uçlarını bulup kuvvetle muhtemel ayağımın altına yapışmış olduğunu bildiğim bir jumper kablosunu lehimlemekle sonlanıyor sövüşlerim ve Ananın ki diye devam ediyor, enerji vermem ile birlikte telefonda wifi yayınlarını listediğim zaman gördüğüm isim ile. Uygulamasını açıp bağlanıyorum ve BINGO. Bizim evin camlarının sunduğu kadar netlikte bir görüntü. Surat ifademi herhalde en iyi şu dakikalar açıklar. (Bkz : Link ). Şükür seviyesinde insanız, Murat Göğebakan gibi zor sanattır hasretlik demek mümkün değil belki ama zor sanattır çaresizlik demekte hakkımız zira bölümü sonuncu bitirmişiz neticede. Hele bir kere uzatmayı gör ha derdi Murat Göğebakan abim paragrafın burasında. Çık diyorum aklımdan çık bu kamerayı hesap makinesi içerisine gömmeyi (Demiyorum elbette, nereden geldiğini bilmediğim hesap makinesini parçalamıştım bile).

Bizde laboratuvar sınavları gruplu olur ve bir önceki grup kapıdan çıkarken milli maç için sahaya inen Tuncay Şanlı gibi girerdik laboratuvara. Sadece o çıkan grup ile aynı sorular geldiğini bildiğimiz diğer grup oyuncularıyla konuşmak yasaktı ve sadece 10-15 saniye yüzlerini görür ya da görmezdik. Ama anlardım o sınavı batırıp batıramayacağımı (Hoş batırmadığım sınav çok çok az oldu). Peki ya bizim gibi bitik tayfadan insanlar benim hesap makinesini alarak sınava girse ve bizde kapıda beklerken wifi ağına bağlanarak kağıdın üzerinde bulunan sonrasında yüzleşeceğimiz o soruları görsek nasıl olurdu ? (Allah’ım lütfen sabahın 8.30’unda sınava giren ilk grup içinde olmayayım…). Ya abi o değilde bu NFT işlerine kafam vallahi basmıyor, Refik Anadol’un eseri 1.2 milyon dolara satılmış NFT lobisi var diyolaa…

Hayde…