Comparing version 0.0.6 to 9.0.0
337
index.js
const events = require('events'); | ||
const binary = require('binary'); | ||
const put = require('put'); | ||
const HID = require('node-hid'); | ||
@@ -67,2 +68,3 @@ | ||
this.productId = 0x5020; | ||
this.dataflashLength = 2048; | ||
@@ -133,3 +135,5 @@ this.configurationLength = 1088; | ||
this.emit('error', err); | ||
// This.disconnect(); | ||
if (err.toString() === 'Error: could not read from HID device') { | ||
this.disconnect(); | ||
} | ||
}); | ||
@@ -242,3 +246,40 @@ this.connected = true; | ||
encodeProfile(profile) { | ||
let flags = profile.Material; | ||
if (profile.IsTemperatureDominant) { | ||
flags += 0x10; | ||
} | ||
if (profile.IsCelcius) { | ||
flags += 0x20; | ||
} | ||
if (profile.IsResistanceLocked) { | ||
flags += 0x40; | ||
} | ||
if (profile.IsEnabled) { | ||
flags += 0x80; | ||
} | ||
let bin = put() | ||
.put(Buffer.from((profile.Name + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000').substr(0, 8), 'ascii')) | ||
.word8(flags) | ||
.word8(profile.PreheatType) | ||
.word8(profile.SelectedCurve) | ||
.word8(profile.PreheatTime * 100) | ||
.word8(profile.PreheatDelay * 10) | ||
.word16le(profile.PreheatPower) | ||
.word16le(profile.Power * 10) | ||
.word16le(profile.Temperature) | ||
.word16le(profile.Resistance * 1000) | ||
.word16le(profile.TCR) | ||
.word8(profile.PIRegulatorIsEnabled) | ||
.word8(profile.PIRegulatorRange) | ||
.word16le(profile.PIRegulatorPValue) | ||
.word16le(profile.PIRegulatorIValue) | ||
.buffer(); | ||
return bin; | ||
} | ||
parseProfile(buf) { | ||
const l = buf.length; | ||
const data = binary.parse(buf) | ||
@@ -279,3 +320,18 @@ .buffer('Name', 8) | ||
encodeDeviceInfo(config) { | ||
let bin = put() | ||
.word8(config.SettingsVersion) | ||
.put(Buffer.from(config.ProductId, 'ascii')) | ||
.word32le(Number(config.HardwareVersion) * 100) | ||
.word16le(config.MaxDevicePower * 10) | ||
.word8(config.NumberOfBatteries) | ||
.word8(config.DisplaySize) | ||
.word32le(config.FirmwareVersion) | ||
.word32le(config.FirmwareBuild) | ||
.buffer(); | ||
return bin; | ||
} | ||
parseDeviceInfo(buf) { | ||
const l = buf.length; | ||
const data = binary.parse(buf) | ||
@@ -303,3 +359,14 @@ .word8u('SettingsVersion') | ||
encodeGeneralConfiguration(config) { | ||
let bin = put() | ||
.word8(config.SelectedProfile) | ||
.word8(config.SmartMode) | ||
.word8(config.SmartRange) | ||
.buffer(); | ||
return bin; | ||
} | ||
parseGeneralConfiguration(buf) { | ||
const l = buf.length; | ||
const data = binary.parse(buf) | ||
@@ -317,3 +384,125 @@ .word8u('SelectedProfile') | ||
encodeUiConfiguration(config) { | ||
let bin = put() | ||
.word8(config.ClicksVW0) | ||
.word8(config.ClicksVW1) | ||
.word8(config.ClicksVW2) | ||
.word8(config.ClicksTC0) | ||
.word8(config.ClicksTC1) | ||
.word8(config.ClicksTC2) | ||
.word8(config.ShortcutsVW0InStandby) | ||
.word8(config.ShortcutsVW0InEditMain) | ||
.word8(config.ShortcutsVW0InSelector) | ||
.word8(config.ShortcutsVW0InMenu) | ||
.word8(config.ShortcutsVW1InStandby) | ||
.word8(config.ShortcutsVW1InEditMain) | ||
.word8(config.ShortcutsVW1InSelector) | ||
.word8(config.ShortcutsVW1InMenu) | ||
.word8(config.ShortcutsVW2InStandby) | ||
.word8(config.ShortcutsVW2InEditMain) | ||
.word8(config.ShortcutsVW2InSelector) | ||
.word8(config.ShortcutsVW2InMenu) | ||
.word8(config.ShortcutsTC0InStandby) | ||
.word8(config.ShortcutsTC0InEditMain) | ||
.word8(config.ShortcutsTC0InSelector) | ||
.word8(config.ShortcutsTC0InMenu) | ||
.word8(config.ShortcutsTC1InStandby) | ||
.word8(config.ShortcutsTC1InEditMain) | ||
.word8(config.ShortcutsTC1InSelector) | ||
.word8(config.ShortcutsTC1InMenu) | ||
.word8(config.ShortcutsTC2InStandby) | ||
.word8(config.ShortcutsTC2InEditMain) | ||
.word8(config.ShortcutsTC2InSelector) | ||
.word8(config.ShortcutsTC2InMenu) | ||
.word8(config.ClassicSkinVWLine1 + (config.ClassicSkinVWLine1Puff ? 0x80 : 0)) | ||
.word8(config.ClassicSkinVWLine2 + (config.ClassicSkinVWLine2Puff ? 0x80 : 0)) | ||
.word8(config.ClassicSkinVWLine3 + (config.ClassicSkinVWLine3Puff ? 0x80 : 0)) | ||
.word8(config.ClassicSkinVWLine4 + (config.ClassicSkinVWLine4Puff ? 0x80 : 0)) | ||
.word8(config.ClassicSkinTCLine1 + (config.ClassicSkinTCLine1Puff ? 0x80 : 0)) | ||
.word8(config.ClassicSkinTCLine2 + (config.ClassicSkinTCLine2Puff ? 0x80 : 0)) | ||
.word8(config.ClassicSkinTCLine3 + (config.ClassicSkinTCLine3Puff ? 0x80 : 0)) | ||
.word8(config.ClassicSkinTCLine4 + (config.ClassicSkinTCLine4Puff ? 0x80 : 0)) | ||
.word8(config.CircleSkinVWLine1) | ||
.word8(config.CircleSkinVWLine2) | ||
.word8(config.CircleSkinVWLine3 + (config.CircleSkinVWLine3Puff ? 0x80 : 0)) | ||
.word8(config.CircleSkinTCLine1) | ||
.word8(config.CircleSkinTCLine2) | ||
.word8(config.CircleSkinTCLine3 + (config.CircleSkinTCLine3Puff ? 0x80 : 0)) | ||
.word8(config.FoxySkinVWLine1 + (config.FoxySkinVWLine1Puff ? 0x80 : 0)) | ||
.word8(config.FoxySkinVWLine2 + (config.FoxySkinVWLine2Puff ? 0x80 : 0)) | ||
.word8(config.FoxySkinVWLine3 + (config.FoxySkinVWLine3Puff ? 0x80 : 0)) | ||
.word8(config.FoxySkinTCLine1 + (config.FoxySkinTCLine1Puff ? 0x80 : 0)) | ||
.word8(config.FoxySkinTCLine2 + (config.FoxySkinTCLine2Puff ? 0x80 : 0)) | ||
.word8(config.FoxySkinTCLine3 + (config.FoxySkinTCLine3Puff ? 0x80 : 0)) | ||
.word8(config.SmallSkinVWLine1 + (config.SmallSkinVWLine1Puff ? 0x80 : 0)) | ||
.word8(config.SmallSkinVWLine2 + (config.SmallSkinVWLine2Puff ? 0x80 : 0)) | ||
.word8(config.SmallSkinTCLine1 + (config.SmallSkinTCLine1Puff ? 0x80 : 0)) | ||
.word8(config.SmallSkinTCLine2 + (config.SmallSkinTCLine2Puff ? 0x80 : 0)) | ||
.word8(config.Brightness) | ||
.word8(config.DimTimeout) | ||
.word8(config.DimTimeoutLocked) | ||
.word8(config.DimTimeoutCharging) | ||
.word8(config.ShowLogoDelay) | ||
.word8(config.ShowClockDelay) | ||
.word8(config.IsFlipped ? 1 : 0) | ||
.word8(config.IsStealthMode ? 1 : 0) | ||
.word8(config.WakeUpByPlusMinus ? 1 : 0) | ||
.word8(config.IsPowerStep1W ? 1 : 0) | ||
.word8(config.IsTemperatureStep1C2F ? 1 : 0) | ||
.word8(config.ChargeScreenType) | ||
.word8(config.ChargeExtraType) | ||
.word8(config.IsLogoEnabled ? 1 : 0) | ||
.word8(config.IsClassicMenu ? 1 : 0) | ||
.word8(config.ClockType) | ||
.word8(config.IsClockOnMainScreen ? 1 : 0) | ||
.word8(config.ScreensaveDuration) | ||
.word8(config.PuffScreenDelay) | ||
.word8(config.PuffsTimeFormat) | ||
.word8(config.MainScreenSkin) | ||
.word8(config.IsUpDownSwapped ? 1 : 0) | ||
.word8(config.ShowChargingInStealth ? 1 : 0) | ||
.word8(config.ShowScreensaverInStealth) | ||
.word8(config.ClockOnClickInStealth ? 1 : 0) | ||
.word8(config.FiveClicks) | ||
.word32le(config.PuffsCount) | ||
.word32le(config.PuffsTime) | ||
.word16le(config.Year) | ||
.word8(config.Month) | ||
.word8(config.Day) | ||
.word8(config.Hour) | ||
.word8(config.Minute) | ||
.word8(config.Second) | ||
.buffer(); | ||
return bin; | ||
} | ||
parseUiConiguration(buf) { | ||
var l = buf.length; | ||
const data = binary.parse(buf) | ||
@@ -503,6 +692,29 @@ .word8u('ClicksVW0') | ||
delete data.buf; | ||
return {data, buf}; | ||
} | ||
encodeCustomBattery(battery) { | ||
let bin = put() | ||
.put(Buffer.from((battery.Name + '\u0000\u0000\u0000\u0000').substr(0, 4), 'ascii')) | ||
.buffer(); | ||
for (let i = 0; i < 11; i++) { | ||
bin = Buffer.concat([bin, put() | ||
.word16le(battery.PercentsVoltage[i].Percents) | ||
.word16le(battery.PercentsVoltage[i].Voltage * 100) | ||
.buffer() | ||
]); | ||
} | ||
bin = Buffer.concat([bin, put() | ||
.word16le(battery.Cutoff * 100) | ||
.buffer() | ||
]); | ||
return bin; | ||
} | ||
parseCustomBattery(buf) { | ||
const l = buf.length; | ||
const data = binary.parse(buf) | ||
@@ -545,3 +757,20 @@ .buffer('Name', 4) | ||
encodeTFRTable(table) { | ||
let bin = put() | ||
.put(Buffer.from((table.Name + '\u0000\u0000\u0000\u0000').substr(0, 4), 'ascii')) | ||
.buffer(); | ||
for (let i = 0; i < 7; i++) { | ||
bin = Buffer.concat([bin, put() | ||
.word16le(table.Points[i].Temperature) | ||
.word16le(table.Points[i].Factor * 10000) | ||
.buffer() | ||
]); | ||
} | ||
return bin; | ||
} | ||
parseTFRTable(buf) { | ||
const l = buf.length; | ||
const data = binary.parse(buf) | ||
@@ -576,3 +805,20 @@ .buffer('Name', 4) | ||
encodePowerCurve(curve) { | ||
let bin = put() | ||
.put(Buffer.from((curve.Name + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000').substr(0, 8), 'ascii')) | ||
.buffer(); | ||
for (let i = 0; i < 12; i++) { | ||
bin = Buffer.concat([bin, put() | ||
.word8(curve.Points[i].Time * 10) | ||
.word8(curve.Points[i].Percent) | ||
.buffer() | ||
]); | ||
} | ||
return bin; | ||
} | ||
parsePowerCurve(buf) { | ||
const l = buf.length; | ||
const data = binary.parse(buf) | ||
@@ -607,3 +853,51 @@ .buffer('Name', 8) | ||
encodeAdvancedConfiguration(config) { | ||
let bin = put() | ||
.word8(config.ShuntCorrection) | ||
.word8(config.BatteryModel) | ||
.buffer(); | ||
for (let i = 0; i < 3; i++) { | ||
bin = Buffer.concat([bin, this.encodeCustomBattery(config.CustomBatteryProfiles[i])]) | ||
} | ||
bin = Buffer.concat([bin, put() | ||
.word8(config.RtcMode) | ||
.word8(config.IsUsbCharge ? 1 : 0) | ||
.word8(config.ResetCountersOnStartup ? 1 : 0) | ||
.buffer() | ||
]); | ||
for (let i = 0; i < 8; i++) { | ||
bin = Buffer.concat([bin, this.encodeTFRTable(config.TFRTables[i])]) | ||
} | ||
bin = Buffer.concat([bin, put() | ||
.word8(config.PuffCutOff * 10) | ||
.buffer() | ||
]); | ||
for (let i = 0; i < 8; i++) { | ||
bin = Buffer.concat([bin, this.encodePowerCurve(config.PowerCurves[i])]) | ||
} | ||
bin = Buffer.concat([bin, put() | ||
.word8((((config.BatteryVoltageOffset1 < 0)) ? 0x80 : 0) + ((config.BatteryVoltageOffset1 * 100) & 0x7f)) | ||
.word8((((config.BatteryVoltageOffset2 < 0)) ? 0x80 : 0) + ((config.BatteryVoltageOffset2 * 100) & 0x7f)) | ||
.word8((((config.BatteryVoltageOffset3 < 0)) ? 0x80 : 0) + ((config.BatteryVoltageOffset3 * 100) & 0x7f)) | ||
.word8((((config.BatteryVoltageOffset4 < 0)) ? 0x80 : 0) + ((config.BatteryVoltageOffset4 * 100) & 0x7f)) | ||
.word8(config.CheckTCR ? 1 : 0) | ||
.word8(config.UsbNoSleep ? 1 : 0) | ||
.word8(config.DeepSleepMode) | ||
.word8(config.DeepSleepDelay) | ||
.word16le(config.PowerLimit * 10) | ||
.word8(config.InternalResistance * 1000) | ||
.buffer() | ||
]); | ||
return bin; | ||
} | ||
parseAdvancedConfiguration(buf) { | ||
const l = buf.length; | ||
const data = binary.parse(buf) | ||
@@ -682,2 +976,7 @@ .word8u('ShuntCorrection') | ||
data3.BatteryVoltageOffset1 /= 100; | ||
data3.BatteryVoltageOffset2 /= 100; | ||
data3.BatteryVoltageOffset3 /= 100; | ||
data3.BatteryVoltageOffset4 /= 100; | ||
data3.CheckTCR = Boolean(data3.CheckTCR); | ||
@@ -693,3 +992,34 @@ data3.UsbNoSleep = Boolean(data3.UsbNoSleep); | ||
encodeConfiguration(config) { | ||
let bin = Buffer.from([]); | ||
bin = Buffer.concat([bin, this.encodeDeviceInfo(config)]); | ||
for (let i = 0; i < 8; i++) { | ||
bin = Buffer.concat([bin, this.encodeProfile(config.profiles[i])]); | ||
} | ||
bin = Buffer.concat([bin, this.encodeGeneralConfiguration(config)]); | ||
bin = Buffer.concat([bin, this.encodeUiConfiguration(config)]); | ||
bin = Buffer.concat([bin, this.encodeAdvancedConfiguration(config)]); | ||
bin = Buffer.concat([bin, Buffer.from(Array.apply(null, Array(58)).map(Number.prototype.valueOf, 0))]); | ||
//this.saveDump('b.bin', bin); | ||
return Array.prototype.slice.call(bin, 0); | ||
} | ||
saveDump(file, buf) { | ||
let str = buf.toString('hex'); | ||
let out = ''; | ||
for (let i = 0; i < str.length; i += 32) { | ||
out += (str.substr(i, 32) + '\n'); | ||
} | ||
require('fs').writeFileSync(file, out); | ||
} | ||
parseConfiguration(buf, callback) { | ||
//this.saveDump('a.bin', buf); | ||
// See https://github.com/TBXin/NFirmwareEditor/blob/master/src/NToolbox/Models/ArcticFoxConfiguration.cs | ||
@@ -796,4 +1126,9 @@ let res = this.parseDeviceInfo(buf); | ||
writeConfiguration(config) { | ||
return this.hidWrite(this.createCommand(this.commands.writeConfiguration, 0, this.configurationLength)) && | ||
this.hidWrite(this.encodeConfiguration(config)); | ||
} | ||
} | ||
module.exports = new ArcticFox(); |
{ | ||
"name": "arcticfox", | ||
"version": "0.0.6", | ||
"version": "9.0.0", | ||
"description": "Module that abstracts the HID communication with the ArcticFox firmware", | ||
@@ -10,6 +10,7 @@ "main": "index.js", | ||
"author": "Sebastian Raff <hq@ccu.io>", | ||
"license": "GPLv3", | ||
"license": "GPL-3.0", | ||
"dependencies": { | ||
"binary": "^0.3.0", | ||
"node-hid": "^0.5.4" | ||
"node-hid": "^0.5.4", | ||
"put": "0.0.6" | ||
}, | ||
@@ -16,0 +17,0 @@ "devDependencies": { |
@@ -17,8 +17,23 @@ # arcticfox | ||
See [test.js](test.js) for examples... | ||
Work in progress... See [test.js](test.js) for examples... | ||
__Versioning:__ The major number of this libs version reflects the SettingsVersion it is made for. | ||
## Related | ||
* https://github.com/hobbyquaker/arcticfox-monitor - A device monitoring tool for vape battery mods with Arcticfox firmware. Works on macOS and Linux. | ||
* https://github.com/hobbyquaker/arcticfox-config - Configuration Tool for Vape Devices / Battery Mods with Arcticfox | ||
Firmware. Works on MacOS and Linux. | ||
## Credits | ||
Based on the work of [NFE Team](https://nfeteam.org/) | ||
* https://github.com/maelstrom2001/ArcticFox | ||
* https://github.com/TBXin/NFirmwareEditor | ||
## License | ||
GPLv3 |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Native code
Supply chain riskContains native code (e.g., compiled binaries or shared libraries). Including native code can obscure malicious behavior.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
102591
9
2076
0
39
3
1
2