Usando xAPI (lata) e CMI5 em simuladores

imagem
Apesar do fato de o SCORM 2004 ainda "manter a posição", é hora de começar a manter novos padrões. Hoje vamos tentar lidar com xAPI / TinCab / CMI5. Definitivamente, testaremos o código nos sites oficiais www.SCORM.com e www.adlnet.gov .



Portanto, a API Tin Can é uma especificação para programas de ensino à distância que permite que os sistemas de treinamento se comuniquem, rastreando e registrando sessões de treinamento de todos os tipos. As informações sobre atividades de aprendizagem são armazenadas em um banco de dados especial - o armazenamento de registros de aprendizagem (LRS).



Mais detalhes podem ser encontrados em books.ifmo.ru/file/pdf/1772.pdf Parte



2 deste artigo - https://habr.com/ru/post/508882/



Recursos da API Tin Can:



API Tin Can - a substituição proposta para a especificação SCORM

API Tin Can nos permite registrar qualquer experiência de aprendizado, o que nos dá uma imagem mais completa do aprendizado de uma pessoa específica. A

API Tin Can remove as restrições de dados impostas pela LMS

Tin Can API pode fornecer assistência valiosa aos departamentos educacionais, comparando dados de qualidade realizando trabalho com dados de treinamento, aumentando assim a eficácia do treinamento.



Isso é teoria, agora pratique.



Ao trabalhar com o SCORM, tudo era relativamente simples, era necessário "definir" os valores das variáveis ​​fixas ou obter os valores das variáveis ​​fixas.



Bem, por exemplo ...



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);


Isto é aproximadamente como tudo foi feito ... Agora no xAPI ... A



seguir, é apresentada uma lista dos LRSs nos quais eu realizei o teste de interação (é necessário registrar e obter login / aprovação, respectivamente) ...





Para interagir com xAPI em C ++, precisamos de CURL e alguma biblioteca para trabalhar com JSON (cJSON por exemplo) ...



Então, o uso de xAPI pode ser feito assim:



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)


Tudo, olhamos para os registros no 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;
}





All Articles