starlink-node
Version:
Node.js package for communication and control of Starlink terminals locally
260 lines • 10.7 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.StarlinkClient = void 0;
const grpc = __importStar(require("@grpc/grpc-js"));
const protoLoader = __importStar(require("@grpc/proto-loader"));
class StarlinkClient {
constructor(host = '192.168.100.1', port = 9200) {
this.connected = false;
this.host = host;
this.port = port;
}
async connect() {
if (this.connected)
return;
try {
// Note: In a real implementation, you would need the actual Starlink protobuf definitions
// This is a simplified version for demonstration
const protoPath = './proto/starlink.proto';
const packageDefinition = protoLoader.loadSync(protoPath, {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
});
const protoDescriptor = grpc.loadPackageDefinition(packageDefinition);
this.client = new protoDescriptor.SpaceX.API.Device.Device(`${this.host}:${this.port}`, grpc.credentials.createInsecure());
this.connected = true;
}
catch (error) {
throw new Error(`Failed to connect to Starlink terminal at ${this.host}:${this.port}: ${error}`);
}
}
async getStatus() {
await this.connect();
return new Promise((resolve, reject) => {
const request = { getStatus: {} };
this.client.Handle(request, (error, response) => {
if (error) {
reject(this.createStarlinkError(error));
return;
}
try {
const status = response.dishGetStatus;
resolve({
uptime: status.deviceInfo?.uptimeS || 0,
hardware: {
version: status.deviceInfo?.hardwareVersion || 'unknown',
dishId: status.deviceInfo?.id || 'unknown',
utcOffsetS: status.deviceInfo?.utcOffsetS || 0,
},
state: this.mapState(status.state),
alertsEnabled: status.alertsEnabled || false,
fractionObstructed: status.obstructionStats?.fractionObstructed || 0,
secondsUntilSwUpdate: status.secondsUntilSwUpdate || 0,
popPingDropRate: status.popPingDropRate || 0,
downlinkThroughputBps: status.downlinkThroughputBps || 0,
uplinkThroughputBps: status.uplinkThroughputBps || 0,
popPingLatencyMs: status.popPingLatencyMs || 0,
});
}
catch (parseError) {
reject(new Error(`Failed to parse status response: ${parseError}`));
}
});
});
}
async getStats() {
await this.connect();
return new Promise((resolve, reject) => {
const request = { dishGetStats: {} };
this.client.Handle(request, (error, response) => {
if (error) {
reject(this.createStarlinkError(error));
return;
}
try {
const stats = response.dishGetStats;
resolve({
pingStats: {
latencyMs: stats.popPingLatencyMs || 0,
dropRate: stats.popPingDropRate || 0,
jitterMs: stats.popPingJitterMs || 0,
},
throughputStats: {
downloadBps: stats.downlinkThroughputBps || 0,
uploadBps: stats.uplinkThroughputBps || 0,
},
obstructionStats: {
fractionObstructed: stats.obstructionStats?.fractionObstructed || 0,
validS: stats.obstructionStats?.validS || 0,
wedgeFractionObstructed: stats.obstructionStats?.wedgeFractionObstructed || [],
wedgeAbsFractionObstructed: stats.obstructionStats?.wedgeAbsFractionObstructed || [],
},
});
}
catch (parseError) {
reject(new Error(`Failed to parse stats response: ${parseError}`));
}
});
});
}
async getLocation() {
await this.connect();
return new Promise((resolve, reject) => {
const request = { getLocation: {} };
this.client.Handle(request, (error, response) => {
if (error) {
reject(this.createStarlinkError(error));
return;
}
try {
const location = response.getLocation;
resolve({
latitude: location.lla?.lat,
longitude: location.lla?.lon,
altitude: location.lla?.alt,
obstructionData: {
fractionObstructed: location.obstructionStats?.fractionObstructed || 0,
timeObstructed: location.obstructionStats?.timeObstructed || 0,
validS: location.obstructionStats?.validS || 0,
},
});
}
catch (parseError) {
reject(new Error(`Failed to parse location response: ${parseError}`));
}
});
});
}
async getHistory() {
await this.connect();
return new Promise((resolve, reject) => {
const request = { dishGetHistory: {} };
this.client.Handle(request, (error, response) => {
if (error) {
reject(this.createStarlinkError(error));
return;
}
try {
const history = response.dishGetHistory;
resolve({
currentHourlyStats: {
downloadThroughputBps: history.currentHourlyStats?.downloadThroughputBps || [],
uploadThroughputBps: history.currentHourlyStats?.uploadThroughputBps || [],
pingLatencyMs: history.currentHourlyStats?.pingLatencyMs || [],
pingDropRate: history.currentHourlyStats?.pingDropRate || [],
obstructedS: history.currentHourlyStats?.obstructedS || [],
},
popPingStats: {
latencyMs: history.popPingStats?.latencyMs || [],
dropRate: history.popPingStats?.dropRate || [],
},
});
}
catch (parseError) {
reject(new Error(`Failed to parse history response: ${parseError}`));
}
});
});
}
async reboot() {
await this.connect();
return new Promise((resolve, reject) => {
const request = { reboot: {} };
this.client.Handle(request, (error, response) => {
if (error) {
reject(this.createStarlinkError(error));
return;
}
resolve();
});
});
}
async stow() {
await this.connect();
return new Promise((resolve, reject) => {
const request = { dishStow: { unstow: false } };
this.client.Handle(request, (error, response) => {
if (error) {
reject(this.createStarlinkError(error));
return;
}
resolve();
});
});
}
async unstow() {
await this.connect();
return new Promise((resolve, reject) => {
const request = { dishStow: { unstow: true } };
this.client.Handle(request, (error, response) => {
if (error) {
reject(this.createStarlinkError(error));
return;
}
resolve();
});
});
}
async disconnect() {
if (this.client && this.connected) {
this.client.close();
this.connected = false;
}
}
mapState(state) {
const stateMap = {
'UNKNOWN': 'OFFLINE',
'OFFLINE': 'OFFLINE',
'BOOTING': 'BOOTING',
'SEARCHING': 'SEARCHING',
'CONNECTED': 'CONNECTED',
'STOWED': 'STOWED',
'THERMAL_SHUTDOWN': 'THERMAL_SHUTDOWN',
};
return stateMap[state] || 'OFFLINE';
}
createStarlinkError(grpcError) {
const error = new Error(grpcError.message);
error.code = grpcError.code;
error.details = grpcError.details;
return error;
}
}
exports.StarlinkClient = StarlinkClient;
//# sourceMappingURL=client.js.map