Verwendung von xAPI (Tin Can) und CMI5 in Simulatoren

Bild
Trotz der Tatsache, dass SCORM 2004 immer noch „die Position innehat“, ist es an der Zeit, neue Standards beizubehalten. Heute werden wir versuchen, mit xAPI / TinCab / CMI5 umzugehen. Wir werden den Code auf jeden Fall auf den offiziellen Websites www.SCORM.com und www.adlnet.gov testen .



Die Tin Can API ist also eine Spezifikation für Fernlernprogramme, mit denen Trainingssysteme miteinander kommunizieren können, indem sie Trainingseinheiten aller Art verfolgen und aufzeichnen. Informationen zu Bildungsaktivitäten werden in einer speziellen Datenbank gespeichert - dem Learning Records Store (LRS).



Weitere Informationen finden Sie unter books.ifmo.ru/file/pdf/1772.pdf Teil



2 dieses Artikels - https://habr.com/ru/post/508882/



Funktionen der Tin Can API:



Tin Can API - der vorgeschlagene Ersatz für die SCORM-Spezifikation. Mit der

Tin Can API können Sie alle Lernerfahrungen aufzeichnen, wodurch wir ein vollständigeres Bild des Trainings einer bestimmten Person erhalten. Die

Tin Can API beseitigt die durch die

Tin Can API auferlegten Datenbeschränkungen . Arbeiten mit Trainingsdaten durchführen, wodurch die Effektivität des Trainings erhöht wird.



Das ist Theorie, jetzt Praxis.



Bei der Arbeit mit SCORM war alles relativ einfach. Sie mussten die Werte fester Variablen "einstellen" oder die Werte fester Variablen abrufen.



Nun, zum Beispiel ...



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


So wurde ungefähr alles gemacht ... Jetzt auf xAPI ...



Als nächstes folgt eine Liste der LRSs, für die ich Interaktionstests durchgeführt habe (Registrierung und Erhalt von Login / Pass sind erforderlich) ...





Für die Interaktion mit xAPI in C ++ benötigen wir CURL und eine Bibliothek für die Arbeit mit JSON (z. B. cJSON). Die



Verwendung von xAPI kann dann folgendermaßen erfolgen:



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)


Alles, wir schauen uns die Aufzeichnungen im LRS an.



""



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