lavva.exalushome
Version:
Library implementing communication and abstraction layers for ExalusHome system
471 lines • 26.5 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { Api } from "../../Api";
import { DataFrame, Method, Status } from "../../DataFrame";
import { downloadStringAsCsv, ParseObjToMap } from "../../Helpers";
import { DevicesService } from "../Devices/DevicesService";
import { DeviceResponseType } from "../Devices/IDevice";
import { ExalusConnectionService } from "../ExalusConnectionService";
import { ResponseResult } from "../FieldChangeResult";
import { UpdatesProvider } from "../Updates/UpdatesProvider";
import { StateHistoryErrorCode } from "./IStatesHistoryService";
import { AvailableState, DataRangeType, StateInterval } from "./StatesHistory";
export class StatesHistoryService {
constructor() {
this.DELIM = ";";
this._connection = null;
this._connection = Api.Get(ExalusConnectionService.ServiceName);
}
numToCsv(val) {
const n = typeof val === "string" ? Number(val.replace(",", ".")) : val;
if (!Number.isFinite(n))
return "";
return n.toLocaleString("en-US", { useGrouping: false });
}
quoteIfNeeded(field) {
return field.includes(this.DELIM) || field.includes('"')
? `"${field.replace(/"/g, '""')}"`
: field;
}
ExportPressureToCsv(device, channel, data) {
downloadStringAsCsv(this.ExportAveragingStateValuesToCsv(device, channel, data), `PressureHistory_${device.Name}_${channel}.csv`);
}
ExportBrightnessToCsv(device, channel, data) {
var _a;
if (!((_a = data === null || data === void 0 ? void 0 : data.Data) === null || _a === void 0 ? void 0 : _a.length))
return;
const lines = [];
// ---------- header ----------
const first = data.Data[0];
const header = ["Time", "AggregatedBy"];
header.push(`LuxValue`, `LuxToLinearScale`, `LightCondition`);
lines.push(header.join(this.DELIM));
// ---------- data rows ----------
data.Data.forEach(el => {
var _a, _b, _c;
const row = [
this.quoteIfNeeded(el.Time),
this.quoteIfNeeded(el.AggregatedBy)
];
if (el.Values !== null) {
row.push(this.numToCsv((_a = el.Values) === null || _a === void 0 ? void 0 : _a.LuxValue));
row.push(this.numToCsv((_b = el.Values) === null || _b === void 0 ? void 0 : _b.LuxToLinearScale));
row.push(this.numToCsv((_c = el.Values) === null || _c === void 0 ? void 0 : _c.LightCondition));
}
lines.push(row.join(this.DELIM));
});
downloadStringAsCsv(lines.join("\n"), `BrightnessHistory_${device.Name}_${channel}.csv`);
}
ExportHumidityToCsv(device, channel, data) {
downloadStringAsCsv(this.ExportAveragingStateValuesToCsv(device, channel, data), `HumidityHistory_${device.Name}_${channel}.csv`);
}
ExportWindSpeedToCsv(device, channel, data) {
downloadStringAsCsv(this.ExportAveragingStateValuesToCsv(device, channel, data), `WindSpeedHistory_${device.Name}_${channel}.csv`);
}
ExportTemperatureToCsv(device, channel, data) {
downloadStringAsCsv(this.ExportAveragingStateValuesToCsv(device, channel, data), `TemperatureHistory_${device.Name}_${channel}.csv`);
}
ExportAveragingStateValuesToCsv(device, channel, data) {
var _a;
if (!((_a = data === null || data === void 0 ? void 0 : data.Data) === null || _a === void 0 ? void 0 : _a.length))
return "";
const lines = [];
// ---------- header ----------
const first = data.Data[0];
const header = ["Time", "AggregatedBy"];
header.push(`LastValue`, `Average`, `Mode`, `Min`, `Max`);
lines.push(header.join(this.DELIM));
// ---------- data rows ----------
data.Data.forEach(el => {
var _a, _b, _c, _d, _e;
const row = [
this.quoteIfNeeded(el.Time),
this.quoteIfNeeded(el.AggregatedBy)
];
if (el.Values !== null) {
row.push(this.numToCsv((_a = el.Values) === null || _a === void 0 ? void 0 : _a.LastValue));
row.push(this.numToCsv((_b = el.Values) === null || _b === void 0 ? void 0 : _b.Average));
row.push(this.numToCsv((_c = el.Values) === null || _c === void 0 ? void 0 : _c.Mode));
row.push(this.numToCsv((_d = el.Values) === null || _d === void 0 ? void 0 : _d.Min));
row.push(this.numToCsv((_e = el.Values) === null || _e === void 0 ? void 0 : _e.Max));
}
lines.push(row.join(this.DELIM));
});
return lines.join("\n");
}
ExportEnergyToCsv(device, channel, response) {
var _a, _b;
let data = response.Data;
if (!(data === null || data === void 0 ? void 0 : data.length))
return;
const lines = [];
// ---------- header ----------
const first = data[0];
const header = ["Time", "AggregatedBy"];
(_a = first.Values) === null || _a === void 0 ? void 0 : _a.MeasurementAveragingParameters.forEach((_v, key) => {
header.push(`${key}:LastValue`, `${key}:Average`, `${key}:Mode`, `${key}:Min`, `${key}:Max`);
});
(_b = first.Values) === null || _b === void 0 ? void 0 : _b.MeasurementNonAveragingParmeters.forEach((_v, key) => header.push(`${key}`));
lines.push(header.join(this.DELIM));
// ---------- data rows ----------
data.forEach(el => {
var _a, _b;
const row = [
this.quoteIfNeeded(el.Time),
this.quoteIfNeeded(el.AggregatedBy)
];
(_a = el.Values) === null || _a === void 0 ? void 0 : _a.MeasurementAveragingParameters.forEach(vals => {
row.push(this.numToCsv(vals.LastValue), this.numToCsv(vals.Average), this.numToCsv(vals.Mode), this.numToCsv(vals.Min), this.numToCsv(vals.Max));
});
(_b = el.Values) === null || _b === void 0 ? void 0 : _b.MeasurementNonAveragingParmeters.forEach(v => row.push(this.numToCsv(v)));
lines.push(row.join(this.DELIM));
});
downloadStringAsCsv(lines.join("\n"), `EnergyHistory_${device.Name}_${channel}.csv`);
}
GetStatesByTimeRangeAsync(dev_1, channel_1, responseType_1, timeRange_1, time_1, limit_1, offset_1) {
return __awaiter(this, arguments, void 0, function* (dev, channel, responseType, timeRange, time, limit, offset, orderByDesc = false) {
var _a;
try {
if (!(yield this.DoesSupportPreciseTimeFramesAsync()))
return new ResponseResult(StateHistoryErrorCode.FunctionalityNotSupported, `State history data in time range is not supported with this version of controller software, update software to get this functionality.`);
let device;
if (typeof dev === 'string') {
const result = yield Api.Get(DevicesService.ServiceName).GetDevice(dev);
if (result != null)
device = result;
else
return new ResponseResult(StateHistoryErrorCode.CannotFindDevice, `Device with specified GUID ${dev} does not exist.`);
}
else {
device = dev;
}
if (!device.Channels.any(ch => ch.Number === channel))
return new ResponseResult(StateHistoryErrorCode.InvalidChannelNumber, `Given channel not found in device with guid: ${device.Guid}.`);
if (!device.Channels.any(ch => ch.Number === channel && ch.AvailableResponseTypes.any(r => r.Type === responseType)))
return new ResponseResult(StateHistoryErrorCode.ResponseTypeNotSupported, `Device or channel not supporting requested DeviceResponseType.`);
// Prepare request object
const request = new StateInTimeRangeRequest();
request.DeviceGuid = device.Guid;
request.DeviceChannel = channel;
request.StateInterfaceType = responseType;
request.ReverseOrder = orderByDesc;
request.DataRange = timeRange;
request.Time = time;
request.Limit = limit;
request.Offset = offset;
// Optionally, you could use the 'time' parameter for more precise requests if supported
const result = yield ((_a = this._connection) === null || _a === void 0 ? void 0 : _a.SendAndWaitForResponseAsync(new GetStatesIntimeRangeRequest(request), 30000, false));
if (result == null)
return new ResponseResult(StateHistoryErrorCode.OtherError, `Cannot get state history data - response is null.`);
switch (result.Status) {
case Status.WrongData:
switch (result.Data) {
case "IncorrectLimitValue":
return new ResponseResult(StateHistoryErrorCode.IncorrectLimitValue, `Cannot get state history data - limit value is incorrect!`);
case "IncorrectOffsetValue":
return new ResponseResult(StateHistoryErrorCode.IncorrectOffsetValue, `Cannot get state history data - offset value is incorrect!`);
case "IncorrectArguments":
return new ResponseResult(StateHistoryErrorCode.OtherError, `Cannot get state history data - some parameters are incorrect!`);
case "UnsupportedRange":
return new ResponseResult(StateHistoryErrorCode.OtherError, `Cannot get state history data - time range is incorrect!`);
default:
return new ResponseResult(StateHistoryErrorCode.OtherError, `Cannot get state history data - unknown error!`);
}
case Status.FatalError:
return new ResponseResult(StateHistoryErrorCode.FatalError, `Cannot get state history data - an exception occurred in the controller while reading data!`);
case Status.ResourceDoesNotExists:
return new ResponseResult(StateHistoryErrorCode.ResponseTypeNotSupported, `Device or channel not supporting requested DeviceResponseType or device/channel does not exist. ${result.Data}`);
case Status.OK:
if (result.Data == null)
return new ResponseResult(StateHistoryErrorCode.NoData, `Controller responded with status OK, but response does not contain data!`);
result.Data.AggregateDataList = result.Data.AggregateDataList.map(d => {
d.AggregateData = ParseObjToMap(d.AggregateData);
return d;
});
switch (responseType) {
case DeviceResponseType.MeasuredEnergy:
const parseData = result.Data.Data.map(d => {
const result = d;
result.Values.MeasurementAveragingParameters = ParseObjToMap(d.Values.MeasurementAveragingParameters);
result.Values.MeasurementNonAveragingParmeters = ParseObjToMap(d.Values.MeasurementNonAveragingParmeters);
return result;
});
result.Data.Data = parseData;
return result.Data;
default:
return result.Data;
}
default:
return new ResponseResult(StateHistoryErrorCode.OtherError, `Cannot get state history data - controller responded with response code ${result.Status}`);
}
}
catch (error) {
return new ResponseResult(StateHistoryErrorCode.FatalError, `Cannot get state history data - exception occurs! ${error}`);
}
});
}
GetServiceName() {
return StatesHistoryService.ServiceName;
}
DoesSupportPreciseTimeFramesAsync() {
return __awaiter(this, void 0, void 0, function* () {
let ver = (yield Api.Get(UpdatesProvider.ServiceName).GetSoftwareRuntimeInfoAsync()).SoftwareVersion.split(".");
return Number(ver[0]) > 6 || (Number(ver[0]) === 6 && Number(ver[1]) >= 101);
});
}
GetStatesByIntervalAsync(dev_1, channel_1, responseType_1, stateFrom_1, limit_1, offset_1) {
return __awaiter(this, arguments, void 0, function* (dev, channel, responseType, stateFrom, limit, offset, orderByDesc = false) {
var _a;
try {
if (!(yield this.IsFunctionalitySupportedAsync()))
return new ResponseResult(StateHistoryErrorCode.FunctionalityNotSupported, `State history data is not supported with this version of controller software, update software to get this functionality.`);
let device;
if (typeof dev == 'string') {
const result = yield Api.Get(DevicesService.ServiceName).GetDevice(dev);
if (result != null)
device = result;
else
return new ResponseResult(StateHistoryErrorCode.CannotFindDevice, `Device with specified GUID ${dev} does not exists.`);
}
else {
device = dev;
}
if (!device.Channels.any(ch => ch.Number == channel))
return new ResponseResult(StateHistoryErrorCode.InvalidChannelNumber, `Given channel not found in device with guid: ${device.Guid}.`);
if (!device.Channels.any(ch => ch.Number == channel && ch.AvailableResponseTypes.any(r => r.Type == responseType)))
return new ResponseResult(StateHistoryErrorCode.ResponseTypeNotSupported, `Device or channel not supporting requested DeviceResponseType.`);
const request = new StateDataRequest();
request.DeviceGuid = device.Guid;
request.DeviceChannel = channel;
request.StateInterfaceType = responseType;
request.ReverseOrder = orderByDesc;
request.Limit = limit;
request.Offset = offset;
let ver = (yield Api.Get(UpdatesProvider.ServiceName).GetSoftwareRuntimeInfoAsync()).SoftwareVersion.split(".");
let isSupported = Number(ver[0]) > 6 || (Number(ver[0]) === 6 && Number(ver[1]) >= 101);
if (isSupported) {
switch (stateFrom) {
case StateInterval.ThisHour:
request.Range = StateInterval.Hour;
break;
case StateInterval.ThisDay:
request.Range = StateInterval.Day;
break;
case StateInterval.ThisWeek:
request.Range = StateInterval.Week;
break;
case StateInterval.ThisMonth:
request.Range = StateInterval.Month;
break;
case StateInterval.ThisYear:
request.Range = StateInterval.Year;
break;
}
}
else
request.Range = stateFrom;
const result = yield ((_a = this._connection) === null || _a === void 0 ? void 0 : _a.SendAndWaitForResponseAsync(new GetLastStatesRequest(request), 30000, false));
if (result == null)
return new ResponseResult(StateHistoryErrorCode.OtherError, `Cannot get state history data - response is null.`);
switch (result.Status) {
case Status.WrongData:
switch (result.Data) {
case "IncorrectLimitValue":
return new ResponseResult(StateHistoryErrorCode.IncorrectLimitValue, `Cannot get state history data - limit value is incorrect!`);
case "IncorrectOffsetValue":
return new ResponseResult(StateHistoryErrorCode.IncorrectOffsetValue, `Cannot get state history data - offset value is incorrect!`);
case "IncorrectArguments":
return new ResponseResult(StateHistoryErrorCode.OtherError, `Cannot get state history data - some parameters are incorrect!`);
default:
return new ResponseResult(StateHistoryErrorCode.OtherError, `Cannot get state history data - unknown error!`);
}
case Status.FatalError:
return new ResponseResult(StateHistoryErrorCode.FatalError, `Cannot get state history data - an exception occurred in the controller while reading data!`);
case Status.ResourceDoesNotExists:
return new ResponseResult(StateHistoryErrorCode.ResponseTypeNotSupported, `Device or channel not supporting requested DeviceResponseType.`);
case Status.OK:
if (result.Data == null)
return new ResponseResult(StateHistoryErrorCode.NoData, `Controller responede with status OK, but response does not contain data!`);
result.Data.AggregateDataList = result.Data.AggregateDataList.map(d => {
d.AggregateData = ParseObjToMap(d.AggregateData);
return d;
});
switch (responseType) {
case DeviceResponseType.MeasuredEnergy:
const parseData = result.Data.Data.map(d => {
const result = d;
result.Values.MeasurementAveragingParameters = ParseObjToMap(d.Values.MeasurementAveragingParameters);
result.Values.MeasurementNonAveragingParmeters = ParseObjToMap(d.Values.MeasurementNonAveragingParmeters);
return result;
});
result.Data.Data = parseData;
return result.Data;
default:
return result.Data;
}
default:
return new ResponseResult(StateHistoryErrorCode.OtherError, `Cannot get state history data - controller responded with response code ${result.Status}`);
}
}
catch (error) {
return new ResponseResult(StateHistoryErrorCode.FatalError, `Cannot get state history data - exeption occurs! ${error}`);
}
});
}
GetAvailableStatesAsync() {
return __awaiter(this, void 0, void 0, function* () {
var _a;
try {
const result = yield ((_a = this._connection) === null || _a === void 0 ? void 0 : _a.SendAndWaitForResponseAsync(new GetAvailableStatesRequest(), 12000, false));
if (result == null)
return new ResponseResult(StateHistoryErrorCode.OtherError, `Cannot get available states - response is null.`);
if (result.Status != Status.OK)
return new ResponseResult(StateHistoryErrorCode.OtherError, `Cannot get available states - controller responded with response code ${result.Status}.`);
if (result.Data == null)
return new ResponseResult(StateHistoryErrorCode.NoData, `Cannot get available states - controller responede with status OK, but response does not contain data!`);
return result.Data.map(r => {
const x = new AvailableState();
x.StateInterfaceType = r.StateInterfaceType;
x.StateObjectType = r.StateObjectType;
return x;
});
}
catch (error) {
return new ResponseResult(StateHistoryErrorCode.FatalError, `Cannot get available states - exeption occurs! ${error}`);
}
});
}
IsFunctionalitySupportedAsync() {
return __awaiter(this, void 0, void 0, function* () {
let ver = (yield Api.Get(UpdatesProvider.ServiceName).GetSoftwareRuntimeInfoAsync()).SoftwareVersion.split(".");
return Number(ver[0]) > 3 || (Number(ver[0]) === 3 && Number(ver[1]) >= 42);
});
}
GetAvailableStatesPerChannelAsync(dev, channel) {
return __awaiter(this, void 0, void 0, function* () {
var _a;
let device;
if (typeof dev == 'string') {
const result = yield Api.Get(DevicesService.ServiceName).GetDevice(dev);
if (result != null)
device = result;
else
return new ResponseResult(StateHistoryErrorCode.CannotFindDevice, `Device with specified GUID ${dev} does not exists.`);
}
else {
device = dev;
}
const result = yield ((_a = this._connection) === null || _a === void 0 ? void 0 : _a.SendAndWaitForResponseAsync(new GetAvailableStatesPerChannelRequest(new ChannelParamRequest(device.Guid, channel)), 8000, false));
if (result == null)
return new ResponseResult(StateHistoryErrorCode.OtherError, `Cannot get available states - response is null.`);
switch (result.Status) {
case Status.ResourceDoesNotExists: {
switch (result.Data) {
case "DeviceNotFound":
return new ResponseResult(StateHistoryErrorCode.CannotFindDevice, `Cannot get available states - device does not exists!`);
case "ChannelNotFound":
return new ResponseResult(StateHistoryErrorCode.InvalidChannelNumber, `Cannot get available states - wrong channel!`);
default:
return new ResponseResult(StateHistoryErrorCode.OtherError, `Cannot get available states - unknown error!`);
}
}
case Status.OK: {
if (result.Data == null)
return new ResponseResult(StateHistoryErrorCode.NoData, `Cannot get available states - controller responede with status OK, but response does not contain data!`);
const remappedStates = result.Data.AvailableStates.map(r => {
const x = new AvailableState();
x.StateInterfaceType = r.StateInterfaceType;
x.StateObjectType = r.StateObjectType;
return x;
});
result.Data.AvailableStates = remappedStates;
return result.Data;
}
default:
return new ResponseResult(StateHistoryErrorCode.OtherError, `Cannot get available states - controller responded with response code ${result.Status}`);
}
});
}
}
StatesHistoryService.ServiceName = "StatesHistoryService";
class GetLastStatesRequest extends DataFrame {
constructor(data) {
super();
this.Resource = "/statehistory/states/get/last";
this.Method = Method.Get;
this.Data = data;
}
}
class StateDataRequest {
constructor() {
this.DeviceGuid = "";
this.DeviceChannel = 0;
this.StateInterfaceType = "";
this.Range = StateInterval.Day;
this.ReverseOrder = false;
this.Limit = 0;
this.Offset = 0;
}
}
class GetStatesIntimeRangeRequest extends DataFrame {
constructor(data) {
super();
this.Resource = "/statehistory/states/get/timerange";
this.Method = Method.Get;
this.Data = data;
}
}
class StateInTimeRangeRequest {
constructor() {
this.DeviceGuid = "";
this.DeviceChannel = 0;
this.StateInterfaceType = "";
this.DataRange = DataRangeType.Day;
this.Time = new Date();
this.ReverseOrder = false;
this.Limit = 0;
this.Offset = 0;
}
}
class GetAvailableStatesRequest extends DataFrame {
constructor() {
super();
this.Resource = "/statehistory/states/available";
this.Method = Method.Get;
}
}
class GetAvailableStatesPerChannelRequest extends DataFrame {
constructor(request) {
super();
this.Resource = "/statehistory/states/available/per/channel";
this.Method = Method.Get;
this.Data = request;
}
}
class ChannelParamRequest {
constructor(guid, channel) {
this.DeviceGuid = guid;
this.Channel = channel;
}
}
class AvailableStateResponse {
constructor() {
this.StateInterfaceType = "";
this.StateObjectType = "";
}
}
class AvailableStatePerChannelResponse {
constructor() {
this.DeviceGuid = "";
this.Channel = 0;
this.AvailableStates = [];
}
}
//# sourceMappingURL=StatesHistoryService.js.map