'use strict';
var SYM = SYM || {};
SYM.VOLT = 0x00;
SYM.RSSI = 0x01;
SYM.AH_RIGHT = 0x02;
SYM.AH_LEFT = 0x03;
SYM.THR = 0x04;
SYM.THR1 = 0x05;
SYM.FLY_M = 0x9C;
SYM.ON_M = 0x9B;
SYM.AH_CENTER_LINE = 0x26;
SYM.AH_CENTER_LINE_RIGHT = 0x27;
SYM.AH_CENTER = 0x7E;
SYM.AH_BAR9_0 = 0x80;
SYM.AH_DECORATION = 0x13;
SYM.LOGO = 0xA0;
var FONT = FONT || {};
FONT.initData = function() {
if (FONT.data) {
return;
}
FONT.data = {
// default font file name
loaded_font_file: 'default',
// array of arry of image bytes ready to upload to fc
characters_bytes: [],
// array of array of image bits by character
characters: [],
// an array of base64 encoded image strings by character
character_image_urls: []
}
};
FONT.constants = {
SIZES: {
/** NVM ram size for one font char, actual character bytes **/
MAX_NVM_FONT_CHAR_SIZE: 54,
/** NVM ram field size for one font char, last 10 bytes dont matter **/
MAX_NVM_FONT_CHAR_FIELD_SIZE: 64,
CHAR_HEIGHT: 18,
CHAR_WIDTH: 12,
LINE: 30
},
COLORS: {
// black
0: 'rgba(0, 0, 0, 1)',
// also the value 3, could yield transparent according to
// https://www.sparkfun.com/datasheets/BreakoutBoards/MAX7456.pdf
1: 'rgba(255, 255, 255, 0)',
// white
2: 'rgba(255,255,255, 1)'
}
};
/**
* Each line is composed of 8 asci 1 or 0, representing 1 bit each for a total of 1 byte per line
*/
FONT.parseMCMFontFile = function(data) {
var data = data.split("\n");
// clear local data
FONT.data.characters.length = 0;
FONT.data.characters_bytes.length = 0;
FONT.data.character_image_urls.length = 0;
// make sure the font file is valid
if (data.shift().trim() != 'MAX7456') {
var msg = 'that font file doesnt have the MAX7456 header, giving up';
console.debug(msg);
Promise.reject(msg);
}
var character_bits = [];
var character_bytes = [];
// hexstring is for debugging
FONT.data.hexstring = [];
var pushChar = function() {
FONT.data.characters_bytes.push(character_bytes);
FONT.data.characters.push(character_bits);
FONT.draw(FONT.data.characters.length-1);
//$log.debug('parsed char ', i, ' as ', character);
character_bits = [];
character_bytes = [];
};
for (var i = 0; i < data.length; i++) {
var line = data[i];
// hexstring is for debugging
FONT.data.hexstring.push('0x' + parseInt(line, 2).toString(16));
// every 64 bytes (line) is a char, we're counting chars though, which are 2 bits
if (character_bits.length == FONT.constants.SIZES.MAX_NVM_FONT_CHAR_FIELD_SIZE * (8 / 2)) {
pushChar()
}
for (var y = 0; y < 8; y = y + 2) {
var v = parseInt(line.slice(y, y+2), 2);
character_bits.push(v);
}
character_bytes.push(parseInt(line, 2));
}
// push the last char
pushChar();
return FONT.data.characters;
};
FONT.openFontFile = function($preview) {
return new Promise(function(resolve) {
chrome.fileSystem.chooseEntry({type: 'openFile', accepts: [{extensions: ['mcm']}]}, function (fileEntry) {
FONT.data.loaded_font_file = fileEntry.name;
if (chrome.runtime.lastError) {
console.error(chrome.runtime.lastError.message);
return;
}
fileEntry.file(function (file) {
var reader = new FileReader();
reader.onloadend = function(e) {
if (e.total != 0 && e.total == e.loaded) {
FONT.parseMCMFontFile(e.target.result);
resolve();
}
else {
console.error('could not load whole font file');
}
};
reader.readAsText(file);
});
});
});
};
/**
* returns a canvas image with the character on it
*/
var drawCanvas = function(charAddress) {
var canvas = document.createElement('canvas');
var ctx = canvas.getContext("2d");
// TODO: do we want to be able to set pixel size? going to try letting the consumer scale the image.
var pixelSize = pixelSize || 1;
var width = pixelSize * FONT.constants.SIZES.CHAR_WIDTH;
var height = pixelSize * FONT.constants.SIZES.CHAR_HEIGHT;
canvas.width = width;
canvas.height = height;
for (var y = 0; y < height; y++) {
for (var x = 0; x < width; x++) {
if (!(charAddress in FONT.data.characters)) {
console.log('charAddress', charAddress, ' is not in ', FONT.data.characters.length);
}
var v = FONT.data.characters[charAddress][(y*width)+x];
ctx.fillStyle = FONT.constants.COLORS[v];
ctx.fillRect(x, y, pixelSize, pixelSize);
}
}
return canvas;
};
FONT.draw = function(charAddress) {
var cached = FONT.data.character_image_urls[charAddress];
if (!cached) {
cached = FONT.data.character_image_urls[charAddress] = drawCanvas(charAddress).toDataURL('image/png');
}
return cached;
};
FONT.msp = {
encode: function(charAddress) {
return [charAddress].concat(FONT.data.characters_bytes[charAddress].slice(0,FONT.constants.SIZES.MAX_NVM_FONT_CHAR_SIZE));
}
};
FONT.upload = function($progress) {
return Promise.mapSeries(FONT.data.characters, function(data, i) {
$progress.val((i / FONT.data.characters.length) * 100);
return MSP.promise(MSP_codes.MSP_OSD_CHAR_WRITE, FONT.msp.encode(i));
})
.then(function() {
return MSP.promise(MSP_codes.MSP_SET_REBOOT);
});
};
FONT.preview = function($el) {
$el.empty()
for (var i = 0; i < SYM.LOGO; i++) {
var url = FONT.data.character_image_urls[i];
$el.append('');
}
};
FONT.symbol = function(hexVal) {
return String.fromCharCode(hexVal);
};
var OSD = OSD || {};
// parsed fc output and output to fc, used by to OSD.msp.encode
OSD.initData = function() {
OSD.data = {
video_system: null,
display_items: [],
last_positions: {},
preview: []
};
};
OSD.initData();
OSD.constants = {
VIDEO_TYPES: [
'AUTO',
'PAL',
'NTSC'
],
VIDEO_LINES: {
PAL: 16,
NTSC: 13
},
VIDEO_BUFFER_CHARS: {
PAL: 480,
NTSC: 390
},
AHISIDEBARWIDTHPOSITION: 7,
AHISIDEBARHEIGHTPOSITION: 3,
// order matters, so these are going in an array... pry could iterate the example map instead
DISPLAY_FIELDS: [
{
name: 'MAIN_BATT_VOLTAGE',
default_position: -29,
positionable: true,
preview: FONT.symbol(SYM.VOLT) + '16.8'
},
{
name: 'RSSI_VALUE',
default_position: -59,
positionable: true,
preview: FONT.symbol(SYM.RSSI) + '99'
},
{
name: 'TIMER',
default_position: -39,
positionable: true,
preview: FONT.symbol(SYM.ON_M) + ' 11:11'
},
{
name: 'THROTTLE_POS',
default_position: -9,
positionable: true,
preview: FONT.symbol(SYM.THR) + FONT.symbol(SYM.THR1) + ' 0'
},
{
name: 'CPU_LOAD',
default_position: 26,
positionable: true,
preview: '15'
},
{
name: 'VTX_CHANNEL',
default_position: 1,
positionable: true,
preview: 'CH:1'
},
{
name: 'VOLTAGE_WARNING',
default_position: -80,
positionable: true,
preview: 'LOW VOLTAGE'
},
{
name: 'ARMED',
default_position: -107,
positionable: true,
preview: 'ARMED'
},
{
name: 'DISARMED',
default_position: -109,
positionable: true,
preview: 'DISARMED'
},
{
name: 'ARTIFICIAL_HORIZON',
default_position: -1,
positionable: false
},
{
name: 'HORIZON_SIDEBARS',
default_position: -1,
positionable: false
}
],
};
OSD.updateDisplaySize = function() {
var video_type = OSD.constants.VIDEO_TYPES[OSD.data.video_system];
if (video_type == 'AUTO') {
video_type = 'PAL';
}
// compute the size
OSD.data.display_size = {
x: 30,
y: OSD.constants.VIDEO_LINES[video_type],
total: null
};
};
OSD.msp = {
encodeOther: function() {
return [-1, OSD.data.video_system];
},
encode: function(display_item) {
return [
display_item.index,
specificByte(display_item.position, 0),
specificByte(display_item.position, 1)
];
},
// Currently only parses MSP_MAX_OSD responses, add a switch on payload.code if more codes are handled
decode: function(payload) {
var view = payload.data;
var d = OSD.data;
d.compiled_in = view.getUint8(0, 1);
d.video_system = view.getUint8(1, 1);
d.display_items = [];
// start at the offset from the other fields
for (var i = 2; i < view.byteLength; i = i + 2) {
var v = view.getInt16(i, 1)
var j = d.display_items.length;
var c = OSD.constants.DISPLAY_FIELDS[j];
d.display_items.push({
name: c.name,
index: j,
position: v,
positionable: c.positionable,
preview: c.preview
});
}
OSD.updateDisplaySize();
}
};
OSD.GUI = {};
OSD.GUI.preview = {
onDragStart: function(e) {
var ev = e.originalEvent;
ev.dataTransfer.setData("text/plain", ev.target.id);
ev.dataTransfer.setDragImage($(this).data('field').preview_img, 6, 9);
},
onDragOver: function(e) {
var ev = e.originalEvent;
ev.preventDefault();
ev.dataTransfer.dropEffect = "move"
$(this).css({
background: 'rgba(0,0,0,.5)'
});
},
onDragLeave: function(e) {
// brute force unstyling on drag leave
$(this).removeAttr('style');
},
onDrop: function(e) {
var ev = e.originalEvent;
var position = $(this).removeAttr('style').data('position');
if (position > OSD.data.display_size.total/2) {
position = position - OSD.data.display_size.total;
}
var field_id = parseInt(ev.dataTransfer.getData('text').split('field-')[1])
$('input.'+field_id+'.position').val(position).change();
},
};
TABS.osd = {};
TABS.osd.initialize = function (callback) {
var self = this;
if (GUI.active_tab != 'osd') {
GUI.active_tab = 'osd';
}
$('#content').load("./tabs/osd.html", function () {
// translate to user-selected language
localize();
// Open modal window
new jBox('Modal', {
width: 600,
height: 240,
closeButton: 'title',
animation: false,
attach: $('#fontmanager'),
title: 'OSD Font Manager',
content: $('#fontmanagercontent')
});
// 2 way binding... sorta
function updateOsdView() {
// ask for the OSD config data
MSP.promise(MSP_codes.MSP_OSD_CONFIG)
.then(function(info) {
if (!info.length) {
$('.unsupported').fadeIn();
return;
}
$('.supported').fadeIn();
OSD.msp.decode(info);
// video mode
var $videoTypes = $('.video-types').empty();
for (var i = 0; i < OSD.constants.VIDEO_TYPES.length; i++) {
var type = OSD.constants.VIDEO_TYPES[i];
var $checkbox = $('').append($(''+type+'')
.prop('checked', i === OSD.data.video_system)
.data('type', type)
.data('type', i)
);
$videoTypes.append($checkbox);
}
$videoTypes.find(':radio').click(function(e) {
OSD.data.video_system = $(this).data('type');
MSP.promise(MSP_codes.MSP_SET_OSD_CONFIG, OSD.msp.encodeOther())
.then(function() {
updateOsdView();
});
});
// display fields on/off and position
var $displayFields = $('.display-fields').empty();
for (let field of OSD.data.display_items) {
var checked = (-1 != field.position) ? 'checked' : '';
var $field = $('