在模拟器中使用xAPI(锡罐)和CMI5

图片
尽管SCORM 2004仍然保持“地位”,但现在该是开始维护新标准的时候了。今天,我们将尝试处理xAPI / TinCab / CMI5。确保在官方网站www.SCORM.comwww.adlnet.gov上测试代码



因此,“锡罐API”是远程学习计划的规范,该规范允许培训系统通过跟踪和记录各种培训课程来彼此通信。有关教育活动的信息存储在一个特殊的数据库中-学习记录存储(LRS)。



更多细节,可以发现books.ifmo.ru/file/pdf/1772.pdf部分



这篇文章的2 - https://habr.com/ru/post/508882/



制罐API的特点:



制罐API -建议更换为SCORM规范

制罐API允许你记录任何的学习经验,这为我们提供了一个特定的人的培训更完整的画面。

制罐API删除由规定的数据限制

制罐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列表(分别需要注册和获得登录/通过)。





要在C ++中与xAPI进行交互,我们需要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;
}





All Articles