Uno stupido esempio di blockchain senza peer to peer e senza POW...solo per fare vedere come sono collegati i vari record e come si valida un blocco
------------------------------------------------------------------------
import sqlite3, hashlib, time
conn = sqlite3.connect("blockchain.txt")
c =conn.cursor()
c.execute("CREATE TABLE IF NOT EXISTS blocks (id integer PRIMARY KEY AUTOINCREMENT, tempo integer NOT NULL,data text NOT NULL, hash text NOT NULL, precedentehash text NOT NULL)")
c.execute("DELETE FROM blocks")
#INSERISCE IL PRIMO RECORD
#il precedenteh puo' essere impostato come si desidera
indice = 0
precedentehash = "0"
millis = int(round(time.time() * 1000))
data = "Luca"
hash = hashlib.sha256(str(indice) + precedentehash + str(millis) + data)
hex_dig = hash.hexdigest()
query = "INSERT INTO blocks (tempo,data,hash,precedentehash) VALUES ("+str(millis)+",'"+data+"','" + hex_dig+"','" + precedentehash + "')"
print query
c.execute(query)
conn.commit()
# SECONDO RECORD
c.execute("SELECT * FROM blocks ORDER BY id DESC LIMIT 1")
row = c.fetchone()
precedentehash = row[3]
indice = row[0]+1
millis = int(round(time.time() * 1000))
data ="Innocenti"
hash = hashlib.sha256(str(indice) + precedentehash + str(millis) + data)
hex_dig = hash.hexdigest()
query = "INSERT INTO blocks (tempo,data,hash,precedentehash) VALUES ("+str(millis)+",'"+data+"','" + hex_dig+"','" + precedentehash + "')"
print query
c.execute(query)
conn.commit()
# TERZO RECORD
c.execute("SELECT * FROM blocks ORDER BY id DESC LIMIT 1")
row = c.fetchone()
precedentehash = row[3]
indice = row[0]+1
millis = int(round(time.time() * 1000))
data ="Firenze"
hash = hashlib.sha256(str(indice) + precedentehash + str(millis) + data)
hex_dig = hash.hexdigest()
query = "INSERT INTO blocks (tempo,data,hash,precedentehash) VALUES ("+str(millis)+",'"+data+"','" + hex_dig+"','" + precedentehash + "')"
print query
c.execute(query)
conn.commit()
#un blocco e' valido se
# l'indice e' maggiore del blocco precedente
# il precedenteh del blocco in esame e' uguale all'hash del record precedente
# l'hash del blocco e' conforme
print "---------------------------------------------"
#controlla l'integrita' del blocco 3
c.execute("SELECT * FROM blocks WHERE id=3")
c3 = c.fetchone()
c.execute("SELECT * FROM blocks WHERE id=2")
c2 = c.fetchone()
if c2[0] ==(c3[0]-1):
print "Indice OK"
if c2[3] == c3[4]:
print "HASH precedente Corretto"
precedentehash = c3[4]
millis = c3[1]
data = c3[2]
indice = c3[0]
hash = hashlib.sha256(str(indice) + precedentehash + str(millis) + data)
hex_dig = hash.hexdigest()
if c3[3] == hex_dig:
print "Blocco corretto"
print "---------------------------------------------"
# mostra tutto il DB
c.execute("SELECT * FROM blocks")
rows = c.fetchall()
for r in rows:
print(r)
conn.close()
mercoledì 31 gennaio 2018
Mandelbrot su Ninentendo 3DS homebrew
Avendo a disposizione un Nintendo 3DS con Homebrew perche' non provare a farci girare un proprio programmino? Ovviamente Mandelbrot
Per prima cosa si deve impostare il compilatore scaricabile da qui
nel pacchetto e' disponibile un cartella di esempi con vari target (tra cui 3ds)...io ho modificato il programma in 3ds_examples/graphics/bitmap/24bit-color/ in cui e' mostrato in modo semplice come usare il framebuffer per creare immagini grafiche
una volta compilato il programma, invece di copiarlo sulla SD e metterlo in esecuzione sulla consolle, ho usato l'emulatore Citra che permette l'esecuzione dei file 3dsx ovvero l'output del compilatore
-----------------------------------------------------------------
#include <3ds.h>
#include <stdio.h>
#include <string.h>
#define SCREEN_WIDTH 320
#define SCREEN_HEIGHT 240
double iterazioni = 15;
double r;
float a = -2.0;
float b = -1.2;
float im = -1.2;
float dre = 3.0/SCREEN_WIDTH;
float dim = 2.4/SCREEN_HEIGHT;
float x,y,x_new,y_new;
int k,j,i;
void setPixel(u8 *fb, u16 x, u16 y, u8 red, u8 green, u8 blue) {
fb[3 * (240 - y + (x - 1) * 240)] = blue;
fb[3 * (240 - y + (x - 1) * 240) + 1] = green;
fb[3 * (240 - y + (x - 1) * 240) + 2] = red;
}
int main(int argc, char **argv)
{
gfxInitDefault();
consoleInit(GFX_TOP, NULL);
printf("3DS Mandelbrot");
printf("\x1b[21;16HPress Start to exit.");
gfxSetDoubleBuffering(GFX_BOTTOM, false);
u8* fb = gfxGetFramebuffer(GFX_BOTTOM, GFX_LEFT, NULL, NULL);
for (int i=1;i<=SCREEN_WIDTH;i++)
{
a=a+dre;
b=im;
for (int j=1;j<=SCREEN_HEIGHT;j++)
{
b = b+dim;
x = 0;
y = 0;
for (k=0;k<=iterazioni;k++)
{
x_new = (x*x)-(y*y)+a;
y_new = (2*x*y)+b;
if (((x_new*x_new)+(y_new*y_new))>4)
{
r = k%2;
if (r == 0) setPixel(fb, i, j, 255, 255, 255);
break;
}
x = x_new;
y = y_new;
}
}
}
// Main loop
while (aptMainLoop())
{
hidScanInput();
u32 kDown = hidKeysDown();
if (kDown & KEY_START) break; /
gfxFlushBuffers();
gfxSwapBuffers();
gspWaitForVBlank();
}
gfxExit();
return 0;
}
Per prima cosa si deve impostare il compilatore scaricabile da qui
nel pacchetto e' disponibile un cartella di esempi con vari target (tra cui 3ds)...io ho modificato il programma in 3ds_examples/graphics/bitmap/24bit-color/ in cui e' mostrato in modo semplice come usare il framebuffer per creare immagini grafiche
una volta compilato il programma, invece di copiarlo sulla SD e metterlo in esecuzione sulla consolle, ho usato l'emulatore Citra che permette l'esecuzione dei file 3dsx ovvero l'output del compilatore
-----------------------------------------------------------------
#include <3ds.h>
#include <stdio.h>
#include <string.h>
#define SCREEN_WIDTH 320
#define SCREEN_HEIGHT 240
double iterazioni = 15;
double r;
float a = -2.0;
float b = -1.2;
float im = -1.2;
float dre = 3.0/SCREEN_WIDTH;
float dim = 2.4/SCREEN_HEIGHT;
float x,y,x_new,y_new;
int k,j,i;
void setPixel(u8 *fb, u16 x, u16 y, u8 red, u8 green, u8 blue) {
fb[3 * (240 - y + (x - 1) * 240)] = blue;
fb[3 * (240 - y + (x - 1) * 240) + 1] = green;
fb[3 * (240 - y + (x - 1) * 240) + 2] = red;
}
int main(int argc, char **argv)
{
gfxInitDefault();
consoleInit(GFX_TOP, NULL);
printf("3DS Mandelbrot");
printf("\x1b[21;16HPress Start to exit.");
gfxSetDoubleBuffering(GFX_BOTTOM, false);
u8* fb = gfxGetFramebuffer(GFX_BOTTOM, GFX_LEFT, NULL, NULL);
for (int i=1;i<=SCREEN_WIDTH;i++)
{
a=a+dre;
b=im;
for (int j=1;j<=SCREEN_HEIGHT;j++)
{
b = b+dim;
x = 0;
y = 0;
for (k=0;k<=iterazioni;k++)
{
x_new = (x*x)-(y*y)+a;
y_new = (2*x*y)+b;
if (((x_new*x_new)+(y_new*y_new))>4)
{
r = k%2;
if (r == 0) setPixel(fb, i, j, 255, 255, 255);
break;
}
x = x_new;
y = y_new;
}
}
}
// Main loop
while (aptMainLoop())
{
hidScanInput();
u32 kDown = hidKeysDown();
if (kDown & KEY_START) break; /
gfxFlushBuffers();
gfxSwapBuffers();
gspWaitForVBlank();
}
gfxExit();
return 0;
}
venerdì 12 gennaio 2018
Estimote telemetry su PC
Questo e' un metodo per leggere i dati di telemetria dei beacon Estimote da PC senza quindi appoggiarsi ad dispositivo mobile (telefono)
Si deve installare tramite Node.js il pacchetto
npm install noble
a questo punto si puo' scaricare l'esempio estimote-telemetry.js da GitHub e modificarlo alla bisgona.
si mette in esecuzione tramite
node test.js
questo metodo e' stato testato su Centos 7 con Lenovo T430
test.js
------------------------------------
// Packest from the Estimote family (Telemetry, Connectivity, etc.) are
// broadcast as Service Data (per "ยง 1.11. The Service Data - 16 bit UUID" from
// the BLE spec), with the Service UUID 'fe9a'.
var ESTIMOTE_SERVICE_UUID = 'fe9a';
// Once you obtain the "Estimote" Service Data, here's how to check if it's
// a Telemetry packet, and if so, how to parse it.
function parseEstimoteTelemetryPacket(data) { // data is a 0-indexed byte array/buffer
// byte 0, lower 4 bits => frame type, for Telemetry it's always 2 (i.e., 0b0010)
var frameType = data.readUInt8(0) & 0b00001111;
var ESTIMOTE_FRAME_TYPE_TELEMETRY = 2;
if (frameType != ESTIMOTE_FRAME_TYPE_TELEMETRY) { return; }
// byte 0, upper 4 bits => Telemetry protocol version ("0", "1", "2", etc.)
var protocolVersion = (data.readUInt8(0) & 0b11110000) >> 4;
// this parser only understands version up to 2
// (but at the time of this commit, there's no 3 or higher anyway :wink:)
if (protocolVersion > 2) { return; }
// bytes 1, 2, 3, 4, 5, 6, 7, 8 => first half of the identifier of the beacon
var shortIdentifier = data.toString('hex', 1, 9);
// byte 9, lower 2 bits => Telemetry subframe type
// to fit all the telemetry data, we currently use two packets, "A" (i.e., "0")
// and "B" (i.e., "1")
var subFrameType = data.readUInt8(9) & 0b00000011;
var ESTIMOTE_TELEMETRY_SUBFRAME_A = 0;
var ESTIMOTE_TELEMETRY_SUBFRAME_B = 1;
// ****************
// * SUBFRAME "A" *
// ****************
if (subFrameType == ESTIMOTE_TELEMETRY_SUBFRAME_A) {
// ***** ACCELERATION
// byte 10 => acceleration RAW_VALUE on the X axis
// byte 11 => acceleration RAW_VALUE on the Y axis
// byte 12 => acceleration RAW_VALUE on the Z axis
// RAW_VALUE is a signed (two's complement) 8-bit integer
// RAW_VALUE * 2 / 127.0 = acceleration in "g-unit" (http://www.helmets.org/g.htm)
var acceleration = {
x: data.readInt8(10) * 2 / 127.0,
y: data.readInt8(11) * 2 / 127.0,
z: data.readInt8(12) * 2 / 127.0
};
// ***** MOTION STATE
// byte 15, lower 2 bits
// 0b00 ("0") when not moving, 0b01 ("1") when moving
var isMoving = (data.readUInt8(15) & 0b00000011) == 1;
// ***** MOTION STATE DURATION
// byte 13 => "previous" motion state duration
// byte 14 => "current" motion state duration
// e.g., if the beacon is currently still, "current" will state how long
// it's been still and "previous" will state how long it's previously been
// in motion before it stopped moving
//
// motion state duration is composed of two parts:
// - lower 6 bits is a NUMBER (unsigned 6-bit integer)
// - upper 2 bits is a unit:
// - 0b00 ("0") => seconds
// - 0b01 ("1") => minutes
// - 0b10 ("2") => hours
// - 0b11 ("3") => days if NUMBER is < 32
// if it's >= 32, then it's "NUMBER - 32" weeks
var parseMotionStateDuration = function(byte) {
var number = byte & 0b00111111;
var unitCode = (byte & 0b11000000) >> 6;
var unit;
if (unitCode == 0) {
unit = 'seconds';
} else if (unitCode == 1) {
unit = 'minutes';
} else if (unitCode == 2) {
unit = 'hours';
} else if (unitCode == 3 && number < 32) {
unit = 'days';
} else {
unit = 'weeks';
number = number - 32;
}
return {number: number, unit: unit};
}
var motionStateDuration = {
previous: parseMotionStateDuration(data.readUInt8(13)),
current: parseMotionStateDuration(data.readUInt8(14))
};
// ***** GPIO
// byte 15, upper 4 bits => state of GPIO pins, one bit per pin
// 0 = state "low", 1 = state "high"
var gpio = {
pin0: (data.readUInt8(15) & 0b00010000) >> 4 ? 'high' : 'low',
pin1: (data.readUInt8(15) & 0b00100000) >> 5 ? 'high' : 'low',
pin2: (data.readUInt8(15) & 0b01000000) >> 6 ? 'high' : 'low',
pin3: (data.readUInt8(15) & 0b10000000) >> 7 ? 'high' : 'low',
};
// ***** ERROR CODES
var errors;
if (protocolVersion == 2) {
// in protocol version "2"
// byte 15, bits 2 & 3
// bit 2 => firmware error
// bit 3 => clock error (likely, in beacons without Real-Time Clock, e.g.,
// Proximity Beacons, the internal clock is out of sync)
errors = {
hasFirmwareError: ((data.readUInt8(15) & 0b00000100) >> 2) == 1,
hasClockError: ((data.readUInt8(15) & 0b00001000) >> 3) == 1
};
} else if (protocolVersion == 1) {
// in protocol version "1"
// byte 16, lower 2 bits
// bit 0 => firmware error
// bit 1 => clock error
errors = {
hasFirmwareError: (data.readUInt8(16) & 0b00000001) == 1,
hasClockError: ((data.readUInt8(16) & 0b00000010) >> 1) == 1
};
} else if (protocolVersion == 0) {
// in protocol version "0", error codes are in subframe "B" instead
}
// ***** ATMOSPHERIC PRESSURE
var pressure;
if (protocolVersion == 2) {
// added in protocol version "2"
// bytes 16, 17, 18, 19 => atmospheric pressure RAW_VALUE
// RAW_VALUE is an unsigned 32-bit integer, little-endian encoding,
// i.e., least-significant byte comes first
// e.g., if bytes are 16th = 0x12, 17th = 0x34, 18th = 0x56, 19th = 0x78
// then the value is 0x78563412
// RAW_VALUE / 256.0 = atmospheric pressure in pascals (Pa)
// note that unlike what you see on the weather forecast, this value is
// not normalized to the sea level!
pressure = data.readUInt32LE(16) / 256.0;
}
return {
shortIdentifier,
frameType: 'Estimote Telemetry', subFrameType: 'A', protocolVersion,
acceleration, isMoving, motionStateDuration, pressure, gpio, errors
};
// ****************
// * SUBFRAME "B" *
// ****************
} else if (subFrameType == ESTIMOTE_TELEMETRY_SUBFRAME_B) {
// ***** MAGNETIC FIELD
// byte 10 => normalized magnetic field RAW_VALUE on the X axis
// byte 11 => normalized magnetic field RAW_VALUE on the Y axis
// byte 12 => normalized magnetic field RAW_VALUE on the Z axis
// RAW_VALUE is a signed (two's complement) 8-bit integer
// RAW_VALUE / 128.0 = normalized value, between -1 and 1
// the value will be 0 if the sensor hasn't been calibrated yet
var magneticField = {
x: data.readInt8(10) / 128.0,
y: data.readInt8(11) / 128.0,
z: data.readInt8(12) / 128.0
};
// ***** AMBIENT LIGHT
// byte 13 => ambient light level RAW_VALUE
// the RAW_VALUE byte is split into two halves
// pow(2, RAW_VALUE_UPPER_HALF) * RAW_VALUE_LOWER_HALF * 0.72 = light level in lux (lx)
var ambientLightUpper = (data.readUInt8(13) & 0b11110000) >> 4;
var ambientLightLower = data.readUInt8(13) & 0b00001111;
var ambientLightLevel = Math.pow(2, ambientLightUpper) * ambientLightLower * 0.72;
// ***** BEACON UPTIME
// byte 14 + 6 lower bits of byte 15 (i.e., 14 bits total)
// - the lower 12 bits (i.e., byte 14 + lower 4 bits of byte 15) are
// a 12-bit unsigned integer
// - the upper 2 bits (i.e., bits 4 and 5 of byte 15) denote the unit:
// 0b00 = seconds, 0b01 = minutes, 0b10 = hours, 0b11 = days
var uptimeUnitCode = (data.readUInt8(15) & 0b00110000) >> 4;
var uptimeUnit;
switch (uptimeUnitCode) {
case 0: uptimeUnit = 'seconds'; break;
case 1: uptimeUnit = 'minutes'; break;
case 2: uptimeUnit = 'hours'; break;
case 3: uptimeUnit = 'days'; break;
}
var uptime = {
number: ((data.readUInt8(15) & 0b00001111) << 8) | data.readUInt8(14),
unit: uptimeUnit
};
// ***** AMBIENT TEMPERATURE
// upper 2 bits of byte 15 + byte 16 + lower 2 bits of byte 17
// => ambient temperature RAW_VALUE, signed (two's complement) 12-bit integer
// RAW_VALUE / 16.0 = ambient temperature in degrees Celsius
var temperatureRawValue =
((data.readUInt8(17) & 0b00000011) << 10) |
(data.readUInt8(16) << 2) |
((data.readUInt8(15) & 0b11000000) >> 6);
if (temperatureRawValue > 2047) {
// JavaScript way to convert an unsigned integer to a signed one (:
temperatureRawValue = temperatureRawValue - 4096;
}
temperature = temperatureRawValue / 16.0;
// ***** BATTERY VOLTAGE
// upper 6 bits of byte 17 + byte 18 => battery voltage in mini-volts (mV)
// (unsigned 14-bit integer)
// if all bits are set to 1, it means it hasn't been measured yet
var batteryVoltage =
(data.readUInt8(18) << 6) |
((data.readUInt8(17) & 0b11111100) >> 2);
if (batteryVoltage == 0b11111111111111) { batteryVoltage = undefined; }
// ***** ERROR CODES
// byte 19, lower 2 bits
// see subframe A documentation of the error codes
// starting in protocol version 1, error codes were moved to subframe A,
// thus, you will only find them in subframe B in Telemetry protocol ver 0
var errors;
if (protocolVersion == 0) {
errors = {
hasFirmwareError: (data.readUInt8(19) & 0b00000001) == 1,
hasClockError: ((data.readUInt8(19) & 0b00000010) >> 1) == 1
};
}
// ***** BATTERY LEVEL
// byte 19 => battery level, between 0% and 100%
// if all bits are set to 1, it means it hasn't been measured yet
// added in protocol version 1
var batteryLevel;
if (protocolVersion >= 1) {
batteryLevel = data.readUInt8(19);
if (batteryLevel == 0b11111111) { batteryLevel = undefined; }
}
return {
shortIdentifier,
frameType: 'Estimote Telemetry', subFrameType: 'B', protocolVersion,
magneticField, ambientLightLevel, temperature,
uptime, batteryVoltage, batteryLevel, errors
};
}
}
// example how to scan & parse Estimote Telemetry packets with noble
var noble = require('noble');
noble.on('stateChange', function(state) {
console.log('state has changed', state);
if (state == 'poweredOn') {
var serviceUUIDs = [ESTIMOTE_SERVICE_UUID]; // Estimote Service
var allowDuplicates = true;
noble.startScanning(serviceUUIDs, allowDuplicates, function(error) {
if (error) {
console.log('error starting scanning', error);
} else {
console.log('started scanning');
}
});
}
});
noble.on('discover', function(peripheral) {
var data = peripheral.advertisement.serviceData.find(function(el) {
return el.uuid == ESTIMOTE_SERVICE_UUID;
}).data;
var telemetryPacket = parseEstimoteTelemetryPacket(data);
if (telemetryPacket) {
if (telemetryPacket['subFrameType'] == "A"){
console.log(telemetryPacket['shortIdentifier']);
//console.log(telemetryPacket['acceleration']);
console.log("AX " + telemetryPacket['acceleration']['x']);
console.log("AY " + telemetryPacket['acceleration']['y']);
console.log("AZ " + telemetryPacket['acceleration']['z']);
}
}
});
Si deve installare tramite Node.js il pacchetto
npm install noble
a questo punto si puo' scaricare l'esempio estimote-telemetry.js da GitHub e modificarlo alla bisgona.
si mette in esecuzione tramite
node test.js
questo metodo e' stato testato su Centos 7 con Lenovo T430
test.js
------------------------------------
// Packest from the Estimote family (Telemetry, Connectivity, etc.) are
// broadcast as Service Data (per "ยง 1.11. The Service Data - 16 bit UUID" from
// the BLE spec), with the Service UUID 'fe9a'.
var ESTIMOTE_SERVICE_UUID = 'fe9a';
// Once you obtain the "Estimote" Service Data, here's how to check if it's
// a Telemetry packet, and if so, how to parse it.
function parseEstimoteTelemetryPacket(data) { // data is a 0-indexed byte array/buffer
// byte 0, lower 4 bits => frame type, for Telemetry it's always 2 (i.e., 0b0010)
var frameType = data.readUInt8(0) & 0b00001111;
var ESTIMOTE_FRAME_TYPE_TELEMETRY = 2;
if (frameType != ESTIMOTE_FRAME_TYPE_TELEMETRY) { return; }
// byte 0, upper 4 bits => Telemetry protocol version ("0", "1", "2", etc.)
var protocolVersion = (data.readUInt8(0) & 0b11110000) >> 4;
// this parser only understands version up to 2
// (but at the time of this commit, there's no 3 or higher anyway :wink:)
if (protocolVersion > 2) { return; }
// bytes 1, 2, 3, 4, 5, 6, 7, 8 => first half of the identifier of the beacon
var shortIdentifier = data.toString('hex', 1, 9);
// byte 9, lower 2 bits => Telemetry subframe type
// to fit all the telemetry data, we currently use two packets, "A" (i.e., "0")
// and "B" (i.e., "1")
var subFrameType = data.readUInt8(9) & 0b00000011;
var ESTIMOTE_TELEMETRY_SUBFRAME_A = 0;
var ESTIMOTE_TELEMETRY_SUBFRAME_B = 1;
// ****************
// * SUBFRAME "A" *
// ****************
if (subFrameType == ESTIMOTE_TELEMETRY_SUBFRAME_A) {
// ***** ACCELERATION
// byte 10 => acceleration RAW_VALUE on the X axis
// byte 11 => acceleration RAW_VALUE on the Y axis
// byte 12 => acceleration RAW_VALUE on the Z axis
// RAW_VALUE is a signed (two's complement) 8-bit integer
// RAW_VALUE * 2 / 127.0 = acceleration in "g-unit" (http://www.helmets.org/g.htm)
var acceleration = {
x: data.readInt8(10) * 2 / 127.0,
y: data.readInt8(11) * 2 / 127.0,
z: data.readInt8(12) * 2 / 127.0
};
// ***** MOTION STATE
// byte 15, lower 2 bits
// 0b00 ("0") when not moving, 0b01 ("1") when moving
var isMoving = (data.readUInt8(15) & 0b00000011) == 1;
// ***** MOTION STATE DURATION
// byte 13 => "previous" motion state duration
// byte 14 => "current" motion state duration
// e.g., if the beacon is currently still, "current" will state how long
// it's been still and "previous" will state how long it's previously been
// in motion before it stopped moving
//
// motion state duration is composed of two parts:
// - lower 6 bits is a NUMBER (unsigned 6-bit integer)
// - upper 2 bits is a unit:
// - 0b00 ("0") => seconds
// - 0b01 ("1") => minutes
// - 0b10 ("2") => hours
// - 0b11 ("3") => days if NUMBER is < 32
// if it's >= 32, then it's "NUMBER - 32" weeks
var parseMotionStateDuration = function(byte) {
var number = byte & 0b00111111;
var unitCode = (byte & 0b11000000) >> 6;
var unit;
if (unitCode == 0) {
unit = 'seconds';
} else if (unitCode == 1) {
unit = 'minutes';
} else if (unitCode == 2) {
unit = 'hours';
} else if (unitCode == 3 && number < 32) {
unit = 'days';
} else {
unit = 'weeks';
number = number - 32;
}
return {number: number, unit: unit};
}
var motionStateDuration = {
previous: parseMotionStateDuration(data.readUInt8(13)),
current: parseMotionStateDuration(data.readUInt8(14))
};
// ***** GPIO
// byte 15, upper 4 bits => state of GPIO pins, one bit per pin
// 0 = state "low", 1 = state "high"
var gpio = {
pin0: (data.readUInt8(15) & 0b00010000) >> 4 ? 'high' : 'low',
pin1: (data.readUInt8(15) & 0b00100000) >> 5 ? 'high' : 'low',
pin2: (data.readUInt8(15) & 0b01000000) >> 6 ? 'high' : 'low',
pin3: (data.readUInt8(15) & 0b10000000) >> 7 ? 'high' : 'low',
};
// ***** ERROR CODES
var errors;
if (protocolVersion == 2) {
// in protocol version "2"
// byte 15, bits 2 & 3
// bit 2 => firmware error
// bit 3 => clock error (likely, in beacons without Real-Time Clock, e.g.,
// Proximity Beacons, the internal clock is out of sync)
errors = {
hasFirmwareError: ((data.readUInt8(15) & 0b00000100) >> 2) == 1,
hasClockError: ((data.readUInt8(15) & 0b00001000) >> 3) == 1
};
} else if (protocolVersion == 1) {
// in protocol version "1"
// byte 16, lower 2 bits
// bit 0 => firmware error
// bit 1 => clock error
errors = {
hasFirmwareError: (data.readUInt8(16) & 0b00000001) == 1,
hasClockError: ((data.readUInt8(16) & 0b00000010) >> 1) == 1
};
} else if (protocolVersion == 0) {
// in protocol version "0", error codes are in subframe "B" instead
}
// ***** ATMOSPHERIC PRESSURE
var pressure;
if (protocolVersion == 2) {
// added in protocol version "2"
// bytes 16, 17, 18, 19 => atmospheric pressure RAW_VALUE
// RAW_VALUE is an unsigned 32-bit integer, little-endian encoding,
// i.e., least-significant byte comes first
// e.g., if bytes are 16th = 0x12, 17th = 0x34, 18th = 0x56, 19th = 0x78
// then the value is 0x78563412
// RAW_VALUE / 256.0 = atmospheric pressure in pascals (Pa)
// note that unlike what you see on the weather forecast, this value is
// not normalized to the sea level!
pressure = data.readUInt32LE(16) / 256.0;
}
return {
shortIdentifier,
frameType: 'Estimote Telemetry', subFrameType: 'A', protocolVersion,
acceleration, isMoving, motionStateDuration, pressure, gpio, errors
};
// ****************
// * SUBFRAME "B" *
// ****************
} else if (subFrameType == ESTIMOTE_TELEMETRY_SUBFRAME_B) {
// ***** MAGNETIC FIELD
// byte 10 => normalized magnetic field RAW_VALUE on the X axis
// byte 11 => normalized magnetic field RAW_VALUE on the Y axis
// byte 12 => normalized magnetic field RAW_VALUE on the Z axis
// RAW_VALUE is a signed (two's complement) 8-bit integer
// RAW_VALUE / 128.0 = normalized value, between -1 and 1
// the value will be 0 if the sensor hasn't been calibrated yet
var magneticField = {
x: data.readInt8(10) / 128.0,
y: data.readInt8(11) / 128.0,
z: data.readInt8(12) / 128.0
};
// ***** AMBIENT LIGHT
// byte 13 => ambient light level RAW_VALUE
// the RAW_VALUE byte is split into two halves
// pow(2, RAW_VALUE_UPPER_HALF) * RAW_VALUE_LOWER_HALF * 0.72 = light level in lux (lx)
var ambientLightUpper = (data.readUInt8(13) & 0b11110000) >> 4;
var ambientLightLower = data.readUInt8(13) & 0b00001111;
var ambientLightLevel = Math.pow(2, ambientLightUpper) * ambientLightLower * 0.72;
// ***** BEACON UPTIME
// byte 14 + 6 lower bits of byte 15 (i.e., 14 bits total)
// - the lower 12 bits (i.e., byte 14 + lower 4 bits of byte 15) are
// a 12-bit unsigned integer
// - the upper 2 bits (i.e., bits 4 and 5 of byte 15) denote the unit:
// 0b00 = seconds, 0b01 = minutes, 0b10 = hours, 0b11 = days
var uptimeUnitCode = (data.readUInt8(15) & 0b00110000) >> 4;
var uptimeUnit;
switch (uptimeUnitCode) {
case 0: uptimeUnit = 'seconds'; break;
case 1: uptimeUnit = 'minutes'; break;
case 2: uptimeUnit = 'hours'; break;
case 3: uptimeUnit = 'days'; break;
}
var uptime = {
number: ((data.readUInt8(15) & 0b00001111) << 8) | data.readUInt8(14),
unit: uptimeUnit
};
// ***** AMBIENT TEMPERATURE
// upper 2 bits of byte 15 + byte 16 + lower 2 bits of byte 17
// => ambient temperature RAW_VALUE, signed (two's complement) 12-bit integer
// RAW_VALUE / 16.0 = ambient temperature in degrees Celsius
var temperatureRawValue =
((data.readUInt8(17) & 0b00000011) << 10) |
(data.readUInt8(16) << 2) |
((data.readUInt8(15) & 0b11000000) >> 6);
if (temperatureRawValue > 2047) {
// JavaScript way to convert an unsigned integer to a signed one (:
temperatureRawValue = temperatureRawValue - 4096;
}
temperature = temperatureRawValue / 16.0;
// ***** BATTERY VOLTAGE
// upper 6 bits of byte 17 + byte 18 => battery voltage in mini-volts (mV)
// (unsigned 14-bit integer)
// if all bits are set to 1, it means it hasn't been measured yet
var batteryVoltage =
(data.readUInt8(18) << 6) |
((data.readUInt8(17) & 0b11111100) >> 2);
if (batteryVoltage == 0b11111111111111) { batteryVoltage = undefined; }
// ***** ERROR CODES
// byte 19, lower 2 bits
// see subframe A documentation of the error codes
// starting in protocol version 1, error codes were moved to subframe A,
// thus, you will only find them in subframe B in Telemetry protocol ver 0
var errors;
if (protocolVersion == 0) {
errors = {
hasFirmwareError: (data.readUInt8(19) & 0b00000001) == 1,
hasClockError: ((data.readUInt8(19) & 0b00000010) >> 1) == 1
};
}
// ***** BATTERY LEVEL
// byte 19 => battery level, between 0% and 100%
// if all bits are set to 1, it means it hasn't been measured yet
// added in protocol version 1
var batteryLevel;
if (protocolVersion >= 1) {
batteryLevel = data.readUInt8(19);
if (batteryLevel == 0b11111111) { batteryLevel = undefined; }
}
return {
shortIdentifier,
frameType: 'Estimote Telemetry', subFrameType: 'B', protocolVersion,
magneticField, ambientLightLevel, temperature,
uptime, batteryVoltage, batteryLevel, errors
};
}
}
// example how to scan & parse Estimote Telemetry packets with noble
var noble = require('noble');
noble.on('stateChange', function(state) {
console.log('state has changed', state);
if (state == 'poweredOn') {
var serviceUUIDs = [ESTIMOTE_SERVICE_UUID]; // Estimote Service
var allowDuplicates = true;
noble.startScanning(serviceUUIDs, allowDuplicates, function(error) {
if (error) {
console.log('error starting scanning', error);
} else {
console.log('started scanning');
}
});
}
});
noble.on('discover', function(peripheral) {
var data = peripheral.advertisement.serviceData.find(function(el) {
return el.uuid == ESTIMOTE_SERVICE_UUID;
}).data;
var telemetryPacket = parseEstimoteTelemetryPacket(data);
if (telemetryPacket) {
if (telemetryPacket['subFrameType'] == "A"){
console.log(telemetryPacket['shortIdentifier']);
//console.log(telemetryPacket['acceleration']);
console.log("AX " + telemetryPacket['acceleration']['x']);
console.log("AY " + telemetryPacket['acceleration']['y']);
console.log("AZ " + telemetryPacket['acceleration']['z']);
}
}
});
mercoledì 10 gennaio 2018
Rotazioni con quaternioni
I quaternioni sono oggetti matematici 4 dimensionali (aw+bx+cy+dz) costituiti da una parte scalare (detta anche reale) ed una parte vettoriale a 3 dimensioni (detta immaginaria). Il loro uso in informatica e' legata al calcolo della rotazione di oggetti in 3 dimensioni e la rappresentazione geometrica del quaternione e' dato da un vettore da (0,0,0) (b,c,d) attorno al quale si sviluppa una rotazione di valore a. (il vettore rotazione nel caso non lo sia deve essere normalizzato). Un documento di lettura abbastanza semplice si puo' trovare a questo link
i vantaggi sono:
0) i quaternioni sono ottimali per calcolare le rotazioni nello spazio. Per invece individuare la posizione di un punto dello spazio e' piu' semplice la definizione cartesiana o con gli angoli di Eulero
1) rispetto ad una rotazione con gli angoli di Eulero (dove sono necessari 9 parametri) con i quaternioni sono sufficienti 4 parametri
2) la rotazione tramite gli angoli di Eulero e' data dalla successione combinata di rotazioni attorno ai tre assi cartesiani con un ordine non preordinato (per esempio si puo' ruotare prima x che su y ma lo stesso risultato puo' essere ottenuto anche invertendo l'ordine). La rotazione tramite quaternioni e' univoca in quanto quella ottimale
3) legato al punto 2 la rotazione con i quaternioni non e' soggetta a gimbal lock
Per effettuare una rotazione si utilizza una estensione del metodo per i numeri complessi in 2D.
Si crea un quaterione (R) che descriva la rotazione e poi si applica al vettore desiderato(P) con la regola RPR-1
la moltiplicazione dei tre quaternioni segue una regola un po' particolare che si riassume come
Questa codice e' stato ripreso da questo link
---------------------------------------
//coordinate del punto da ruotare
float x_old = 0;
float y_old = 1;
float z_old = 1;
//angolo di rotazione
float angle = (float) Math.toRadians(45.0);
// vettore di rotazione
float xi = 1;
float yi = 1;
float zi = 1;
float norma = (float) Math.sqrt((xi*xi)+(yi*yi)+(zi*zi));
xi = xi/norma;
yi = yi/norma;
zi = zi/norma;
double w = Math.cos(angle/2.0);
double x = xi*Math.sin(angle/2.0);
double y = yi*Math.sin(angle/2.0);
double z = zi*Math.sin(angle/2.0);
float x_new = (float) ((1 - 2*y*y -2*z*z)*x_old + (2*x*y + 2*w*z)*y_old + (2*x*z-2*w*y)*z_old);
float y_new = (float) ((2*x*y - 2*w*z)*x_old + (1 - 2*x*x - 2*z*z)*y_old + (2*y*z + 2*w*x)*z_old);
float z_new = (float) ((2*x*z + 2*w*y)*x_old + (2*y*z - 2*w*x)*y_old + (1 - 2*x*x - 2*y*y)*z_old);
System.out.println(Double.toString(x_new));
System.out.println(Double.toString(y_new));
System.out.println(Double.toString(z_new));
---------------------------------------
Rotazione di un cubo di spigolo 1 orientato come gli asse e con spigolo in (0,0,0) di 45° attorno ad un vettore (0,0,0)(1,1,1). Dati rappresentati con Geogebra (nella tabella a fianco i valori dei punti ruotati)
i vantaggi sono:
0) i quaternioni sono ottimali per calcolare le rotazioni nello spazio. Per invece individuare la posizione di un punto dello spazio e' piu' semplice la definizione cartesiana o con gli angoli di Eulero
1) rispetto ad una rotazione con gli angoli di Eulero (dove sono necessari 9 parametri) con i quaternioni sono sufficienti 4 parametri
2) la rotazione tramite gli angoli di Eulero e' data dalla successione combinata di rotazioni attorno ai tre assi cartesiani con un ordine non preordinato (per esempio si puo' ruotare prima x che su y ma lo stesso risultato puo' essere ottenuto anche invertendo l'ordine). La rotazione tramite quaternioni e' univoca in quanto quella ottimale
3) legato al punto 2 la rotazione con i quaternioni non e' soggetta a gimbal lock
Per effettuare una rotazione si utilizza una estensione del metodo per i numeri complessi in 2D.
Si crea un quaterione (R) che descriva la rotazione e poi si applica al vettore desiderato(P) con la regola RPR-1
la moltiplicazione dei tre quaternioni segue una regola un po' particolare che si riassume come
Questa codice e' stato ripreso da questo link
---------------------------------------
//coordinate del punto da ruotare
float x_old = 0;
float y_old = 1;
float z_old = 1;
//angolo di rotazione
float angle = (float) Math.toRadians(45.0);
// vettore di rotazione
float xi = 1;
float yi = 1;
float zi = 1;
float norma = (float) Math.sqrt((xi*xi)+(yi*yi)+(zi*zi));
xi = xi/norma;
yi = yi/norma;
zi = zi/norma;
double w = Math.cos(angle/2.0);
double x = xi*Math.sin(angle/2.0);
double y = yi*Math.sin(angle/2.0);
double z = zi*Math.sin(angle/2.0);
float x_new = (float) ((1 - 2*y*y -2*z*z)*x_old + (2*x*y + 2*w*z)*y_old + (2*x*z-2*w*y)*z_old);
float y_new = (float) ((2*x*y - 2*w*z)*x_old + (1 - 2*x*x - 2*z*z)*y_old + (2*y*z + 2*w*x)*z_old);
float z_new = (float) ((2*x*z + 2*w*y)*x_old + (2*y*z - 2*w*x)*y_old + (1 - 2*x*x - 2*y*y)*z_old);
System.out.println(Double.toString(x_new));
System.out.println(Double.toString(y_new));
System.out.println(Double.toString(z_new));
---------------------------------------
Rotazione di un cubo di spigolo 1 orientato come gli asse e con spigolo in (0,0,0) di 45° attorno ad un vettore (0,0,0)(1,1,1). Dati rappresentati con Geogebra (nella tabella a fianco i valori dei punti ruotati)
lunedì 8 gennaio 2018
Estimote Eddystone telemetry bridge in Android
Un esempio per ricevere i dati di telemetria dei pacchetti Eddystone (il protocollo di Google per i beacons) e trasmetterli poi ad un server remoto (ho scoperto solo oggi che il nome Eddystone deriva dal faro di Eddystone)
---------------------------------------------
compile 'com.estimote:sdk:1.0.12'
---------------------------------------------
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="innocenti.luca.com.eddyacc">
<uses-permission android:name="android.permisssion.ACCESS_COARSE_LOCATION">
</uses-permission>
<uses-permission android:name="android.permisssion.INTERNET">
</uses-permission>
<application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
---------------------------------------------
package innocenti.luca.com.eddyacc;
import android.os.AsyncTask;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import com.estimote.coresdk.common.config.EstimoteSDK;
import com.estimote.coresdk.recognition.packets.Eddystone;
import com.estimote.coresdk.recognition.packets.EstimoteTelemetry;
import com.estimote.coresdk.service.BeaconManager;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URL;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private BeaconManager beaconManager;
private String scanId;
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
EstimoteSDK.initialize(getApplicationContext(),"eddyacc-ev0","f73efb85963579672c048fc92de64039");
beaconManager = new BeaconManager(this);
beaconManager.setEddystoneListener(new BeaconManager.EddystoneListener() {
@Override public void onEddystonesFound(List<Eddystone> eddystones) {
for (Eddystone e : eddystones){
if (e.telemetry != null)
{
String aa = "http://150.217.73.108/luca/test.php?temperatura="+Double.toString(e.telemetry.temperature);
Double myTaskParameters = e.telemetry.temperature;
new invia().execute(myTaskParameters);
Log.d("Eddy", e.macAddress + " temperature :" + e.telemetry.temperature + " ");
}
}
}
});
beaconManager.setTelemetryListener(new BeaconManager.TelemetryListener() {
@Override public void onTelemetriesFound(List<EstimoteTelemetry> telemetries) {
for (EstimoteTelemetry tlm : telemetries) {
Log.d("TELEMETRY", "beaconID: " + tlm.deviceId +
", temperature: " + tlm.temperature + " °C" + tlm.accelerometer.x);
}
}
});
}
@Override protected void onStart() {
super.onStart();
beaconManager.connect(new BeaconManager.ServiceReadyCallback() {
@Override public void onServiceReady() {
beaconManager.startEddystoneDiscovery();
beaconManager.startTelemetryDiscovery();
}
});
}
@Override protected void onStop() {
super.onStop();
beaconManager.stopEddystoneDiscovery();
beaconManager.stopTelemetryDiscovery();
}
@Override protected void onDestroy(){
super.onDestroy();
beaconManager.disconnect();
}
private class invia extends AsyncTask<Double, Void, String> {
@Override protected String doInBackground(Double... params) {
// These two need to be declared outside the try/catch // so that they can be closed in the finally block. HttpURLConnection urlConnection = null;
Double t = params[0];
try {
URL url = new URL("http://150.217.73.108/luca/test.php?temperatura="+Double.toString(t));
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("GET");
urlConnection.connect();
urlConnection.getInputStream();
} catch (ProtocolException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
}
return null;
}
@Override protected void onPostExecute (String s)
{
super.onPostExecute(s);
}
}
}
Homebrew Launcher su Nintendo 3DS
Cosa fare quando a Natale il bambino ti chiede un esotico giochino Nintendo che e' uscito solo in Giappone??
1) Gli spieghi che le consolle ed in giochi sono distribuiti a seconda di alcune regioni del mondo e che se hai la consolle italiana non puoi giocare con le schede giapponesi??
2) Provi a scaricare la Rom del gioco e la monti sull'emulatore Citra ? (per poi scoprire che anche un I5 funziona al 25% di velocita' del Nintendo originale??
no, ti ricordi che su un mercatino avevi comprato ad un prezzo stracciato una 3DS per poi scoprire a casa che era una consolle americana, non compatibile con le schede in vendita in Italia e metterla in un cassetto per un paio di anni. Avere una consolle vecchia non aggiornata (nel mio caso 10.6.0-31U) permette di far girare un firmware su cui sono conosciuti degli exploit per mandare in esecuzione Homebrew Launcher.
Il modo piu' semplice per ottenere l'exploit e' quello di andare su Homebrew Github e preparare la scheda SD con l'hack per Nintendo 3DS seguendo le istruzioni (molto semplici peraltro).
Con Homebrew launcher non si possono eseguire giochi piratati ma si possono eseguire giochi di qualsiasi regione e quindi alla fine sono riuscito a far funzionare il gioco giapponese
ps : visto che c'ero una partina a DOOM per DOS...
Filtro Kalman su dati accelerometro Android
Una implementazione del filtro kalman su dati sui dati dell'accelerometro in Android (in realta' i dati sono pitch e roll ma derivano dall'accelerometro)
Per il calcolo e' stata utilizzata la libreria JKalman. Dal pacchetto zip si estrae il file .jar e dalla cartella dist e lo si copia nella cartella /libs del progetto Android. A questo punto si clicca destro sul file .jar in Android Studio e si clicca "Add as Library"
a questo punto e' sufficiente creare il filtro in OnCreate
si popola poi l'array dei dati e si ottiene il valore filtrato
Per il calcolo e' stata utilizzata la libreria JKalman. Dal pacchetto zip si estrae il file .jar e dalla cartella dist e lo si copia nella cartella /libs del progetto Android. A questo punto si clicca destro sul file .jar in Android Studio e si clicca "Add as Library"
Si aggiunge la classe ripresa da qui
------------------------------------------------------------------
package innocenti.luca.com.camerakit;
import jkalman.JKalman;
import jama.Matrix;
public class KalmanFilter {
private int variables;
private JKalman kalman;
private Matrix s; // state [x, y, dx, dy, dxy] private Matrix c; // corrected state [x, y, dx, dy, dxy] private Matrix m; // measurement [x]
/* * Inicializa el filtro kalman con 2 variables */ public void initialize2() throws Exception{
double dx, dy;
if(variables != 0){
throw new RuntimeException();
}
variables = 2;
kalman = new JKalman(4, 2);
// constant velocity dx = 0.2;
dy = 0.2;
s = new Matrix(4, 1); // state [x, y, dx, dy, dxy] c = new Matrix(4, 1); // corrected state [x, y, dx, dy, dxy]
m = new Matrix(2, 1); // measurement [x] m.set(0, 0, 0);
m.set(1, 0, 0);
// transitions for x, y, dx, dy double[][] tr = { {1, 0, dx, 0},
{0, 1, 0, dy},
{0, 0, 1, 0},
{0, 0, 0, 1} };
kalman.setTransition_matrix(new Matrix(tr));
// 1s somewhere? kalman.setError_cov_post(kalman.getError_cov_post().identity());
}
/* * Aplica Filtro a variables */ public void push(double x,double y) throws Exception{
m.set(0, 0, x);
m.set(1, 0, y);
c = kalman.Correct(m);
s = kalman.Predict();
}
/* * obtiene arreglo con datos filtrados. */ public double[] getKalmanPoint2() throws Exception{
double[] point = new double[2];
point[0] = c.get(0,0);
point[1] = c.get(1,0);
return point;
}
/* * obtiene arreglo con prediccion de punto. */ public double[] getPredict2() throws Exception{
double[] point = new double[2];
point[0] = s.get(0,0);
point[1] = s.get(1,0);
return point;
}
/* * obtiene cantidad de variables del objeto */ public int getNVariables() throws Exception{
return this.variables;
}
}
------------------------------------------------------------------
a questo punto e' sufficiente creare il filtro in OnCreate
kf = new KalmanFilter();
try {
kf.initialize2();
} catch (Exception e) {
e.printStackTrace();
}
si popola poi l'array dei dati e si ottiene il valore filtrato
try {
kf.push(pi,ro);
} catch (Exception e) {
e.printStackTrace();
}
try {
double[] test = kf.getKalmanPoint2();
Log.d("Kalman", Double.toString(Math.toDegrees(test[0]))+ " "+
Double.toString(Math.toDegrees(test[1])) + " " +Math.toDegrees(pi)+ " "+
Math.toDegrees(ro));
} catch (Exception e) {
e.printStackTrace();
}
venerdì 5 gennaio 2018
16 anni di onorato servizio
Non sempre sono i programmi migliori quelli che durano nel tempo...e questo e' un esempio.
Un mio programma fatto in Visual Basic 6, scritto con i piedi e con gli occhi bendati, e' ancora in funzione e gestisce la fatturazione di un micro impresa dopo 16 anni
Un mio programma fatto in Visual Basic 6, scritto con i piedi e con gli occhi bendati, e' ancora in funzione e gestisce la fatturazione di un micro impresa dopo 16 anni
martedì 2 gennaio 2018
Misurare distanze con realta' aumentata
Un piccolo esperimento sulla possibilita' di misurare distanze utilizzando la realta' aumentata e l'utilizzo di un semplice marker.
Per questa applicazione e' stato modificato l'esempio ARSimple di ARToolKit per Android.
Il codice puo' essere scaricato da GitHub
La cosa piu' difficile e' stato mostrare in tempo reale la distanza sulla interfaccia perche' con OpenGL su Android non esiste un modo semplice di renderizzare testo. La soluzione e' un po' contorta ma funziona. Dalla classe ARSimplerender viene scritta una sharedpreference con all'interno il valore della distanza in mm, nella classe ARSimple (che gestisce la UI) c'e' un timer che legge ogni 0.1 secondi il valore della sharedpreference e la mostra a video nella textview
Per avere un'idea della precisione di tale metodo si puo' consultare il seguente poster o l'articolo
Measuring ARTootKit accuracy in long distance tracking experiments (P. Malbezin,W. Piekarski,B.H. Thomas) 2002 Augmented Reality Toolkit, The First IEEE International Workshop
Per questa applicazione e' stato modificato l'esempio ARSimple di ARToolKit per Android.
Il codice puo' essere scaricato da GitHub
La cosa piu' difficile e' stato mostrare in tempo reale la distanza sulla interfaccia perche' con OpenGL su Android non esiste un modo semplice di renderizzare testo. La soluzione e' un po' contorta ma funziona. Dalla classe ARSimplerender viene scritta una sharedpreference con all'interno il valore della distanza in mm, nella classe ARSimple (che gestisce la UI) c'e' un timer che legge ogni 0.1 secondi il valore della sharedpreference e la mostra a video nella textview
Per avere un'idea della precisione di tale metodo si puo' consultare il seguente poster o l'articolo
Measuring ARTootKit accuracy in long distance tracking experiments (P. Malbezin,W. Piekarski,B.H. Thomas) 2002 Augmented Reality Toolkit, The First IEEE International Workshop
La massima distanza che sono riuscito a misurare e' stata di circa 2.6 m
ps : la applicazione e' stata provata su Nexus 5 e Nexus 5x, Sul 5x la applicazione NON funziona perche' il sensore della camera e' ruotata di 90° rispetto alla norma (questa cosa mi ha ovviamente fatto diventare pazzo per un paio di giorni)
ps: una cosa curiosa. Mentre stavo facendo le prove ho inquadrato senza volerlo la tastiera del Mac e ....e' comparso il cubetto ....in pratica ARToolkit ha confuso il marker Hiro con i simboli sulla tastiera
ps : la applicazione e' stata provata su Nexus 5 e Nexus 5x, Sul 5x la applicazione NON funziona perche' il sensore della camera e' ruotata di 90° rispetto alla norma (questa cosa mi ha ovviamente fatto diventare pazzo per un paio di giorni)
ps: una cosa curiosa. Mentre stavo facendo le prove ho inquadrato senza volerlo la tastiera del Mac e ....e' comparso il cubetto ....in pratica ARToolkit ha confuso il marker Hiro con i simboli sulla tastiera
Iscriviti a:
Post (Atom)
Debugger integrato ESP32S3
Aggiornamento In realta' il Jtag USB funziona anche sui moduli cinesi Il problema risiede nell'ID USB della porta Jtag. Nel modulo...
-
In questo post viene indicato come creare uno scatterplot dinamico basato da dati ripresi da un file csv (nel dettaglio il file csv e' c...
-
La scheda ESP32-2432S028R monta un Esp Dev Module con uno schermo TFT a driver ILI9341 di 320x240 pixels 16 bit colore.Il sito di riferiment...
-
Questo post e' a seguito di quanto gia' visto nella precedente prova Lo scopo e' sempre il solito: creare un sistema che permet...