Использование xAPI (Tin Can) и CMI5 в имитаторах

от автора

image

Несмотря на то, что SCORM 2004 еще «держит позиции», пора начинать поддерживать новые стандарты. Сегодня попробуем разобраться с xAPI / TinCab / CMI5. Обязательно протестируем код на официальных сайтах www.SCORM.com и www.adlnet.gov.

Итак, Tin Can API — это спецификация программ в сфере дистанционного обучения, которая позволяет обучающим системам общаться между собой путём отслеживания и записи учебных занятий всех видов. Информация об учебной деятельности сохраняется в специальную базу — хранилище учебных записей (англ. learning record store, LRS).

Подробно можно прочесть на books.ifmo.ru/file/pdf/1772.pdf

Особенности Tin Can API:

Tin Can API — предлагаемая замена спецификации SCORM
Tin Can API позволяет записывать любой опыт обучения, что дает нам более полную картину обучения конкретного человека
Tin Can API снимает с данных ограничения, накладываемые СДО
Tin Can API способен оказать неоценимую помощь учебным отделам, сопоставляя данные о качестве выполнения работы с учебными данными, тем самым повышая эффективность обучения.

Это теория, теперь практика.

При работе с SCORM все было относительно просто, нужно было «выставить» значения фиксированных переменных или получить значения фиксированных переменных.

Ну например…

min = 0 max= 100 raw_score = 100 scaled = raw_score / max -- Оценка приведенная к диапазону 0..1. 	 ScormSetValue("cmi.score.min", ""..min); -- Минимальная оценка ScormSetValue("cmi.score.max", ""..max); -- Максимальная оценка ScormSetValue("cmi.score.raw", ""..raw_score); -- Полученная оценка ScormSetValue("cmi.score.scaled", ""..scaled); -- Оценка приведенная к диапазону 0..1.  --Объем (количество) 0..1 ScormSetValue("cmi.progress_measure", "1");  ScormSetValue("cmi.success_status", "passed"); ScormSetValue("cmi.completion_status", "completed");  ScormGetValue("cmi.learner_name"); ScormGetValue("cmi.learner_id"); ScormGetValue("cmi.suspend_data"); ScormGetValue("cmi.scaled_passing_score"); ScormGetValue("cmi.completion_threshold");  print ( ScormGetValue("cmi._version")) print ( ScormGetValue("cmi.total_time")) print ( ScormGetValue("cmi.time_limit_action")) print ( ScormGetValue("cmi.max_time_allowed"))  --Запись значений интеракций ScormSetValue("cmi.interactions.0.id","Step1");  ScormSetValue("cmi.interactions.0.description", "17:14:28	Произвести аварийный останов работающих компрессорных станций") ScormSetValue("cmi.interactions.0.result","correct");  ScormSetValue("cmi.interactions.1.id","Step2");  ScormSetValue("cmi.interactions.1.type","fill-in");  ScormSetValue("cmi.interactions.1.objectives.0.id","urn:ADL:objectiveid-0001"); ScormSetValue("cmi.interactions.1.description", "privet");  ScormSetValue("cmi.interactions.1.learner_response", "privet");  ScormSetValue("cmi.interactions.1.timestamp", "2005-10-11T09:00:30"); ScormSetValue("cmi.interactions.1.correct_responses.0.pattern", "privet"); ScormSetValue("cmi.interactions.1.weighting", "1"); --correct, incorrect, unanticipated, neutral , number 0..1 ScormSetValue("cmi.interactions.1.result","unanticipated"); ScormSetValue("cmi.interactions.1.latency", "PT0H0M5.0S");  ScormSetValue ("cmi.comments_from_learner.0.comment",q1); ScormSetValue ("cmi.comments_from_learner.1.comment",q2); 

Примерно так все и делалось… Теперь на xAPI…

Далее идет список тех LRS на которых я выполнял тестирование взаимодействия (необходима регистрация и получение login/pass соответственно)…

Для взаимодействия с xAPI на C++ нам понадобится CURL и какая-нибудь библиотека для работы с JSON (cJSON например)…

Тогда использование xAPI можно выполнять например так:

TinCanAddRecord("actor:::mbox:::mailto:mathmodel@mathmodel.com")			TinCanAddRecord("actor:::name:::mathmodel") TinCanAddRecord("actor:::objectType:::Agent") TinCanAddRecord("verb:::id:::http://adlnet.gov/expapi/verbs/interacted") TinCanAddRecord("object:::id:::http://lcontent.ru/lms1/simulator2") TinCanAddRecord("object:::objectType:::Activity") 			 			TinCanAddRecord("object:::definition:::type:::http://www.lcontent.ru/lms1/simulator1") TinCanAddRecord("object:::definition:::name:::en-US:::mathmodel") TinCanAddRecord("object:::definition:::description:::en-US:::mathmodel log") 			 TinCanAddRecord("object:::extensions:::http://lcontent.ru/upsv/Teapot1 angle:::" .. a1) TinCanAddRecord("object:::extensions:::http://lcontent.ru/upsv/Teapot2 angle:::" .. a2) TinCanAddRecord("object:::extensions:::http://lcontent.ru/upsv/Teapot3 angle:::" .. a3) TinCanAddRecord("object:::extensions:::http://lcontent.ru/upsv/time:::" .. (os.clock() - veryoldtime)) 			 TinCanAddRecord("actor:::mbox:::mailto:maxgammer@gmail.com") TinCanAddRecord("actor:::name:::Maxim Gammer") TinCanAddRecord("actor:::objectType:::Agent") TinCanAddRecord("verb:::id:::http://adlnet.gov/expapi/verbs/interacted") TinCanAddRecord("object:::id:::http://lcontent.ru/lms1/simulator2") TinCanAddRecord("object:::objectType:::Activity") TinCanAddRecord("object:::definition:::type:::http://lcontent.ru/lms1/simulator1") TinCanAddRecord("object:::definition:::name:::en-US:::User move") TinCanAddRecord("object:::definition:::description:::en-US:::User coordinates") TinCanAddRecord("object:::extensions:::http://lcontent.ru/upsv/time:::" .. (os.clock() - veryoldtime)) 			 TinCanAddRecord("object:::extensions:::http://lcontent.ru/upsv/X:::" .. UserData.X) TinCanAddRecord("object:::extensions:::http://lcontent.ru/upsv/Y:::" .. UserData.Y) TinCanAddRecord("object:::extensions:::http://lcontent.ru/upsv/Z:::" .. UserData.Z) TinCanAddRecord("object:::extensions:::http://lcontent.ru/upsv/HeadYaw:::" .. UserData.HeadYaw) TinCanAddRecord("object:::extensions:::http://lcontent.ru/upsv/HeadPitch:::" .. UserData.HeadPitch) TinCanAddRecord("object:::extensions:::http://lcontent.ru/upsv/HeadRoll:::" .. UserData.HeadRoll)

Все, смотрим записи в LRS.

"

InterfaceForTinCan.cpp

#include <stdio.h> #include <fcntl.h> #include <sys/stat.h> #include <string> #include <string.h> #include <iostream> #include <fstream>  #include <vector> #include <map> #include <algorithm> #include <iterator>  #include <curl/curl.h>  //using namespace std;  #ifdef WIN32 	#include "./cJSON.h" #else 	#include "./cJSON.h" #endif  class InterfaceForTinCan { public: 	InterfaceForTinCan(); 	void AddTinCanRecord(std::string str, std::string type); 	void PostToLRS(std::string host, std::string login, std::string password); 	void PostFileToLRS(std::string filename); 	void PostToFile (std::string filename); 	  private: 	std::vector<std::string> split(const std::string& s, const std::string& delim, const bool keep_empty = true ) ;  	//созданные объекты 	std::map <std::string, cJSON *> OBJECTS; 	// 	cJSON *top;  	std::string LRS_host; 	std::string LRS_login; 	std::string LRS_password;    	void PostStringToLRS(std::string zzz);  };   InterfaceForTinCan::InterfaceForTinCan() { 	top=cJSON_CreateObject(); }  void InterfaceForTinCan::AddTinCanRecord(std::string str, std::string type) { 	//1. преобразуем страку в вектор через разделитель (::: или @@@) 	std::vector<std::string> words = split(str, ":::"); 	//последние 2 элемента это поле=значение 	int numOfObject = words.size();  	// 	std::string z =  words [0]; 	if( OBJECTS.end() != OBJECTS.find(z)) 	{ 		//ключ присутствует 	}  	else 	{ 		//создаем 		OBJECTS[z] =cJSON_CreateObject(); 		//клеим к root 		cJSON_AddItemToObject(top,z.c_str(), OBJECTS[z]); 	}  	for (int i=1; i < numOfObject -2; i++) 	{ 		std::string oldz = z; 		z = z + ":::" + words [i];  		if( OBJECTS.end() != OBJECTS.find(z)) 		{ 			//ключ присутствует 		}  		else 		{ 			//создаем 			OBJECTS[z] =cJSON_CreateObject(); 			//клеим к  			cJSON_AddItemToObject(OBJECTS[oldz], words [i].c_str(), OBJECTS[z]); 		} 	}  	std::string value = words [numOfObject-1]; 	if (type=="string") 	{ 		cJSON_AddStringToObject(OBJECTS[z], words [numOfObject-2].c_str(), value.c_str()); 	} 	else if  (type=="number") 	{ 		cJSON_AddNumberToObject(OBJECTS[z], words [numOfObject-2].c_str(), std::stod(value)); 	} 	else if  (type=="bool") 	{ 		bool val = false; 		if ((value=="true")||(value=="TRUE")) val = true; 		cJSON_AddBoolToObject(OBJECTS[z], words [numOfObject-2].c_str(), val); 	} }   void InterfaceForTinCan::PostToLRS(std::string host, std::string login, std::string password) { 	char* out=cJSON_Print(top);	 	std::string zzz = out; 	cJSON_Delete(top); 	OBJECTS.clear();  	printf("%s\n",out); 	free(out); 	top=cJSON_CreateObject();   	LRS_host = host; 	LRS_login = login; 	LRS_password = password;  	PostStringToLRS(zzz); }  void InterfaceForTinCan::PostFileToLRS(std::string filename) { 	std::string zzz; 	std::string line; 	std::ifstream myfile (filename.c_str());     if (myfile.is_open())     { 		while ( myfile.good() ) 		{ 			getline (myfile,line); 			zzz = zzz + line; 		} 		myfile.close();     } 	// 	PostStringToLRS(zzz); }  void InterfaceForTinCan::PostToFile(std::string filename) { 	char* out=cJSON_Print(top);	 	std::string zzz = out; 	cJSON_Delete(top); 	OBJECTS.clear(); 	 	std::ofstream myfile; 	myfile.open (filename); 	myfile << zzz; 	myfile.close();  	free(out); 	top=cJSON_CreateObject(); }  void InterfaceForTinCan::PostStringToLRS(std::string zzz) { 	std::string URL = LRS_host; //"https://cloud.scorm.com/ScormEngineInterface/TCAPI/public/statements"; 	std::string loginpassword = LRS_login + ":" + LRS_password; //"test:test"   	CURL *curl; 	struct curl_slist *headers=NULL;       headers = curl_slist_append(headers, "Accept: application/json");     headers = curl_slist_append( headers, "Content-Type: application/json");     headers = curl_slist_append( headers, "X-Experience-API-Version:1.0.0");     headers = curl_slist_append( headers, "charsets: utf-8");  	curl = curl_easy_init();       if (curl)     {         /* enable verbose for easier tracing */         curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);          curl_easy_setopt(curl, CURLOPT_URL, URL.c_str());         curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST"); //PUT         curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); 		//         curl_easy_setopt( curl, CURLOPT_USERPWD, loginpassword.c_str() ); //"test:test" 		// With the curl command line tool, you disable this with -k/--insecure.         curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false);         curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, false); 		         curl_easy_setopt(curl, CURLOPT_POST, 1);         curl_easy_setopt(curl, CURLOPT_POSTFIELDS, zzz.c_str());          std::cout<< "..." << std::endl; 		CURLcode res = curl_easy_perform(curl);         std::cout<<   std::endl << "..." << std::endl;  		/* Check for errors */          if(res != CURLE_OK)         {             std::cout<< "error:" << std::endl;             fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));             std::cout << std::endl;         }  		curl_easy_cleanup(curl); 	} 	else 	{ 		std::cout << "false" << std::endl; 	} }  std::vector<std::string> InterfaceForTinCan::split(const std::string& s, const std::string& delim, const bool keep_empty)  { 	std::vector <std::string> result; 	if (delim.empty())  	{ 		result.push_back(s); 		return result; 	}     std::string::const_iterator substart = s.begin(), subend;     while (true)  	{         subend = search(substart, s.end(), delim.begin(), delim.end());         std::string temp(substart, subend);         if (keep_empty || !temp.empty()) {             result.push_back(temp);         }         if (subend == s.end()) {             break;         }         substart = subend + delim.size();     }     return result; } 

ссылка на оригинал статьи https://habr.com/ru/post/508624/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *