Initial commit

This commit is contained in:
Paul Fey 2025-06-20 09:47:10 +02:00
commit c310cd7943
19 changed files with 3939 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
dist/

2
app-side/i18n/en-US.po Normal file
View file

@ -0,0 +1,2 @@
msgid "example"
msgstr "This is an example in app-side"

60
app-side/index.js Normal file
View file

@ -0,0 +1,60 @@
import { MessageBuilder } from "../shared/message-side";
const messageBuilder = new MessageBuilder();
async function fetchDirectory(ctx) {
try {
const res = await fetch({
url: "https://directory.spaceapi.io",
method: "GET",
});
const resBody =
typeof res.body === "string" ? JSON.parse(res.body) : res.body;
ctx.response({
data: { success: true, result: resBody },
});
} catch (error) {
ctx.response({
data: { success: false, result: {} },
});
}
}
async function fetchStatus(url, ctx) {
try {
const res = await fetch({
url: url,
method: "GET",
});
const resBody =
typeof res.body === "string" ? JSON.parse(res.body) : res.body;
ctx.response({
data: { success: true, result: resBody },
});
} catch (error) {
ctx.response({
data: { success: false, result: {} },
});
}
}
AppSideService({
onInit() {
messageBuilder.listen(() => {});
messageBuilder.on("request", (ctx) => {
const jsonRpc = messageBuilder.buf2Json(ctx.request.payload);
if (jsonRpc.method === "GET_DIRECTORY") {
return fetchDirectory(ctx);
} else if (jsonRpc.method === "GET_STATUS") {
return fetchStatus(jsonRpc.url, ctx);
}
});
},
onRun() {},
onDestroy() {},
});

28
app.js Normal file
View file

@ -0,0 +1,28 @@
import "./shared/device-polyfill";
import { MessageBuilder } from "./shared/message";
import { getPackageInfo } from "@zos/app";
import * as ble from "@zos/ble";
App({
globalData: {
messageBuilder: null,
},
onCreate(options) {
console.log("app on create invoke");
const { appId } = getPackageInfo();
const messageBuilder = new MessageBuilder({
appId,
appDevicePort: 20,
appSidePort: 0,
ble,
});
this.globalData.messageBuilder = messageBuilder;
messageBuilder.connect();
},
onDestroy(options) {
console.log("app on destroy invoke");
this.globalData.messageBuilder &&
this.globalData.messageBuilder.disConnect();
},
});

57
app.json Normal file
View file

@ -0,0 +1,57 @@
{
"configVersion": "v2",
"app": {
"appId": 28358,
"appName": "ZeppSpace",
"appType": "app",
"version": {
"code": 1,
"name": "1.0.1"
},
"icon": "icon.png",
"vender": "pauljako",
"description": "A ZeppOS App for SpaceAPI"
},
"permissions": [
"data:os.device.info",
"device:os.local_storage",
"device:os.geolocation"
],
"runtime": {
"apiVersion": {
"compatible": "2.0.0",
"target": "2.0.0",
"minVersion": "2.0"
}
},
"debug": false,
"targets": {
"bip5": {
"module": {
"page": {
"pages": ["pages/home/index", "pages/status/index"]
},
"app-side": {
"path": "app-side/index"
}
},
"platforms": [
{
"name": "bip5",
"deviceSource": 8454400
},
{
"name": "bip5-w",
"deviceSource": 8454401
}
],
"designWidth": 380
}
},
"i18n": {
"en-US": {
"appName": "ZeppSpace"
}
},
"defaultLanguage": "en-US"
}

BIN
assets/bip5/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

2
pages/home/i18n/en-US.po Normal file
View file

@ -0,0 +1,2 @@
msgid "example"
msgstr "This is an example in device"

78
pages/home/index.js Normal file
View file

@ -0,0 +1,78 @@
import {
createWidget,
widget,
align,
updateStatusBarTitle,
event,
} from "@zos/ui";
import { log as Logger, px } from "@zos/utils";
import { push } from "@zos/router";
import {
DEFAULT_COLOR,
DEFAULT_COLOR_TRANSPARENT,
} from "../../utils/config/constants";
import { DEVICE_WIDTH } from "../../utils/config/device";
import VisLog from "@silver-zepp/vis-log";
const vis = new VisLog("index.js");
const logger = Logger.getLogger("ZeppSpace");
const { messageBuilder } = getApp()._options.globalData;
let spaces = {};
Page({
state: {},
build() {
updateStatusBarTitle("Spaces (Loading)");
this.fetchData();
},
fetchData() {
messageBuilder
.request({
method: "GET_DIRECTORY",
})
.then((data) => {
vis.log("data received");
const statusSuccess = data["success"];
if (!statusSuccess) {
return;
}
let spaceNames = Object.fromEntries(
Object.entries(data["result"]).sort(([a], [b]) => a.localeCompare(b)),
);
let yPos = 70;
for (let key in spaceNames) {
if (spaceNames.hasOwnProperty(key)) {
spaces[key] = createWidget(widget.TEXT, {
x: 0,
y: px(yPos),
w: px(DEVICE_WIDTH),
h: px(50),
align_h: align.CENTER_H,
align_v: align.CENTER_V,
text_size: px(32),
color: 0xffffff,
text: key,
});
spaces[key].addEventListener(event.CLICK_UP, (info) => {
push({
url: "pages/status/index",
params: {
name: key,
url: spaceNames[key],
},
});
});
yPos += 50;
}
}
updateStatusBarTitle("Spaces");
});
},
});

View file

@ -0,0 +1,2 @@
msgid "example"
msgstr "This is an example in device"

110
pages/status/index.js Normal file
View file

@ -0,0 +1,110 @@
import { createWidget, widget, align, updateStatusBarTitle } from "@zos/ui";
import { log as Logger, px } from "@zos/utils";
import { back } from "@zos/router";
import {
DEFAULT_COLOR,
DEFAULT_COLOR_TRANSPARENT,
} from "../../utils/config/constants";
import { DEVICE_WIDTH } from "../../utils/config/device";
import VisLog from "@silver-zepp/vis-log";
const vis = new VisLog("index.js");
const logger = Logger.getLogger("ZeppSpace");
const { messageBuilder } = getApp()._options.globalData;
let title = "";
let url = "";
Page({
state: {},
onInit(params) {
const data = JSON.parse(params);
if (data["url"] == undefined || data["name"] == undefined) {
back();
return;
}
title = data["name"];
url = data["url"];
},
build() {
updateStatusBarTitle(title + " (Loading)");
this.fetchData();
},
fetchData() {
messageBuilder
.request({
method: "GET_STATUS",
url: url,
})
.then((data) => {
const statusSuccess = data["success"];
if (!statusSuccess) {
return;
}
let open = data["result"]["state"]["open"];
let openText = open ? "Yes" : "No";
createWidget(widget.TEXT, {
x: 0,
y: px(70),
w: px(DEVICE_WIDTH),
h: px(50),
align_h: align.CENTER_H,
align_v: align.CENTER_V,
text_size: px(32),
color: 0xffffff,
text: "Open: " + openText,
});
createWidget(widget.TEXT, {
x: 0,
y: px(120),
w: px(DEVICE_WIDTH),
h: px(50),
align_h: align.CENTER_H,
align_v: align.CENTER_V,
text_size: px(32),
color: 0xffffff,
text:
"Last Updated: " +
this.convertTime(data["result"]["state"]["lastchange"]),
});
updateStatusBarTitle(title);
});
},
convertTime(timestamp) {
var a = new Date(timestamp * 1000);
var today = new Date();
var yesterday = new Date(Date.now() - 86400000);
var months = [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec",
];
var year = a.getFullYear();
var month = months[a.getMonth()];
var date = a.getDate();
var hour = a.getHours() < 10 ? "0" + a.getHours() : a.getHours();
var min = a.getMinutes() < 10 ? "0" + a.getMinutes() : a.getMinutes();
if (a.setHours(0, 0, 0, 0) == today.setHours(0, 0, 0, 0))
return hour + ":" + min;
else if (a.setHours(0, 0, 0, 0) == yesterday.setHours(0, 0, 0, 0))
return "Yesterday, " + hour + ":" + min;
else if (year == today.getFullYear()) return date + " " + month;
else return date + " " + month + " " + year;
},
});

67
shared/data.js Normal file
View file

@ -0,0 +1,67 @@
export function json2buf(json) {
return str2buf(json2str(json))
}
export function json2bin(json) {
return str2bin(json2str(json))
}
export function len(binOrBuf) {
return binOrBuf.byteLength
}
export function buf2json(buf) {
return str2json(buf2str(buf))
}
export function str2json(str) {
return JSON.parse(str)
}
export function json2str(json) {
return JSON.stringify(json)
}
export function str2buf(str) {
return Buffer.from(str, 'utf-8')
}
export function buf2str(buf) {
return buf.toString('utf-8')
}
export function bin2buf(bin) {
return Buffer.from(bin)
}
export function buf2bin(buf) {
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength)
}
export function buf2hex(buf) {
return buf.toString('hex')
}
export function bin2hex(bin) {
return buf2hex(bin2buf(bin))
}
export function bin2json(bin) {
return buf2json(bin2buf(bin))
}
export function bin2str(bin) {
return buf2str(bin2buf(bin))
}
export function str2bin(str) {
return buf2bin(str2buf(str))
}
export function allocOfBin(size = 0) {
return Buffer.alloc(size).buffer
}
export function allocOfBuf(size = 0) {
return Buffer.alloc(size)
}

35
shared/defer.js Normal file
View file

@ -0,0 +1,35 @@
export function Deferred() {
const defer = {}
defer.promise = new Promise(function (resolve, reject) {
defer.resolve = resolve
defer.reject = reject
})
return defer
}
export function delay(ms) {
const defer = Deferred()
setTimeout(defer.resolve, ms)
return defer.promise
}
export function timeout(ms, cb) {
const defer = Deferred()
ms = ms || 1000
const wait = setTimeout(() => {
clearTimeout(wait)
if (cb) {
cb && cb(defer.resolve, defer.reject)
} else {
defer.reject('Timed out in ' + ms + 'ms.')
}
}, ms)
return defer.promise
}

View file

@ -0,0 +1,6 @@
import './es6-promise'
ES6Promise.polyfill()
Promise._setScheduler(function (flush) {
flush && flush()
})

1149
shared/es6-promise.js Normal file

File diff suppressed because it is too large Load diff

42
shared/event.js Normal file
View file

@ -0,0 +1,42 @@
export class EventBus {
constructor() {
this.map = new Map()
}
on(type, cb) {
if (this.map.has(type)) {
this.map.get(type).push(cb)
} else {
this.map.set(type, [cb])
}
}
off(type, cb) {
if (type) {
if (cb) {
const cbs = this.map.get(type)
if (!cbs) return
const index = cbs.findIndex((i) => i === cb)
if (index >= 0) {
cbs.splice(index, 1)
}
} else {
this.map.delete(type)
}
} else {
this.map.clear()
}
}
emit(type, ...args) {
for (let cb of this.map.get(type) ? this.map.get(type) : []) {
cb && cb(...args)
}
}
count(type) {
return this.map.get(type) ? this.map.get(type).length : 0
}
}

1145
shared/message-side.js Normal file

File diff suppressed because it is too large Load diff

1151
shared/message.js Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,2 @@
export const DEFAULT_COLOR = 0xfc6950;
export const DEFAULT_COLOR_TRANSPARENT = 0xfeb4a8;

2
utils/config/device.js Normal file
View file

@ -0,0 +1,2 @@
import { getDeviceInfo } from "@zos/device";
export const { width: DEVICE_WIDTH, height: DEVICE_HEIGHT } = getDeviceInfo();