Neakasa M1
Würde mich freuen wenn man einen Adapter für den Neakasa M1 in den iOBroker entwickeln könnte.
hab leider nur Infos für HA gefunden https://github.com/timniklas/hass-neakasa
hier die HP https://neakasa.de/products/neakasa-m1-cat-litter-box
Vielen Dank schon mal von mir und meinen Katzen
What kind of device or service would you like to see an adapter for? Add name and company of the device, including links to the device and any additional information[...]
Is the device connected to the internet or only available on a local network?
Is an official App or Website available? If yes, please add links
Is an official API including documentation available? If yes, please add links and information[...]
Are other libraries for an integration available? Ideally in JavaScript/npm, but also other programming languages are interesting, add links please
Is this device already integrated in other Smart Home systems? Add links please
Is this device already integrated in homebridge? Might the ham adapter in combination with the homebridge plugin be sufficient? Please try it and add results
Additional context Add any other context or screenshots about the feature request here. If the topic was discussed on the ioBroker forum, please include the link too.
After you create the issue, please vote for yourself in the first post of the issue using the "+1"/"Thumbs up" button
ich habe leider gar keine ahnung davon. hoffe aber chatGPT hat mich verstanden ;)
kann man damit was anfangen?
`/**
- ioBroker Neakasa Adapter
- Adapter skeleton implementing Neakasa Cloud API connectivity
- Extracted structure and partial API flow based on hass-neakasa integration */
const utils = require('@iobroker/adapter-core'); const axios = require('axios');
class Neakasa extends utils.Adapter { constructor(options) { super({ ...options, name: 'neakasa' }); this.devices = {}; this.pollInterval = null; this.token = null; this.baseUrl = 'https://api.neakasa.com'; }
async onReady() {
this.log.info('Starting Neakasa adapter...');
const username = this.config.username;
const password = this.config.password;
this.baseUrl = this.config.cloud_base_url || this.baseUrl;
this.pollInterval = this.config.poll_interval || 60;
try {
await this.login(username, password);
await this.loadDevices();
await this.pollAll();
this.pollTimer = this.setInterval(() => this.pollAll(), this.pollInterval * 1000);
} catch (err) {
this.log.error('Initialization failed: ' + err);
}
}
async login(username, password) {
try {
const res = await axios.post(`${this.baseUrl}/v1/login`, { username, password });
if (res.data && res.data.token) {
this.token = res.data.token;
this.log.info('Successfully authenticated with Neakasa cloud');
} else {
throw new Error('No token received');
}
} catch (err) {
this.log.error('Login failed: ' + err);
throw err;
}
}
async loadDevices() {
try {
const res = await axios.get(`${this.baseUrl}/v1/devices`, { headers: { Authorization: `Bearer ${this.token}` } });
if (!res.data.devices) throw new Error('No devices found');
for (const dev of res.data.devices) {
const id = dev.id || dev.deviceId;
this.devices[id] = dev;
await this.setObjectNotExistsAsync(`devices.${id}`, { type: 'channel', common: { name: dev.name }, native: {} });
await this.createDeviceObjects(id, dev);
}
} catch (err) {
this.log.error('Loading devices failed: ' + err);
}
}
async createDeviceObjects(id, dev) {
const sensors = [
'litter_level', 'wifi_rssi', 'last_stay_time', 'last_usage', 'device_status', 'cat_litter_state', 'bin_state'
];
const switches = [
'kitten_mode', 'child_lock', 'automatic_cover', 'automatic_leveling', 'silent_mode', 'automatic_recovery', 'unstoppable_cycle', 'auto_clean'
];
const buttons = ['clean', 'level'];
for (const s of sensors) {
await this.setObjectNotExistsAsync(`devices.${id}.${s}`, { type: 'state', common: { name: s, type: 'string', role: 'value', read: true, write: false }, native: {} });
}
await this.setObjectNotExistsAsync(`devices.${id}.garbage_can_full`, { type: 'state', common: { name: 'garbage_can_full', type: 'boolean', role: 'indicator', read: true, write: false }, native: {} });
for (const s of switches) {
await this.setObjectNotExistsAsync(`devices.${id}.${s}`, { type: 'state', common: { name: s, type: 'boolean', role: 'switch', read: true, write: true }, native: {} });
}
for (const b of buttons) {
await this.setObjectNotExistsAsync(`devices.${id}.${b}`, { type: 'state', common: { name: b, type: 'boolean', role: 'button', read: false, write: true }, native: {} });
}
await this.subscribeStatesAsync(`devices.${id}.*`);
}
async pollAll() {
for (const id of Object.keys(this.devices)) {
try {
const res = await axios.get(`${this.baseUrl}/v1/devices/${id}/status`, { headers: { Authorization: `Bearer ${this.token}` } });
const data = res.data;
await this.setStateAsync(`devices.${id}.raw`, JSON.stringify(data), true);
const fields = {
litter_level: this._coalesce(data.litterLevel, data.litter_level, data.litterPercent),
wifi_rssi: this._coalesce(data.wifiRssi, data.signal),
last_stay_time: data.lastStayTime,
last_usage: data.lastUsage,
device_status: data.deviceStatus || data.status,
cat_litter_state: data.catLitterState,
bin_state: data.binState
};
for (const [k, v] of Object.entries(fields)) {
if (v !== undefined) await this.setStateAsync(`devices.${id}.${k}`, { val: v, ack: true });
}
await this.setStateAsync(`devices.${id}.garbage_can_full`, { val: data.binState === 'full', ack: true });
if (data.cats) {
for (const cat of data.cats) {
const cname = this._slugify(cat.name || 'cat');
await this.setObjectNotExistsAsync(`devices.${id}.cat_${cname}_kg`, { type: 'state', common: { name: `cat_${cname}_kg`, type: 'number', role: 'value.weight', read: true, write: false }, native: {} });
await this.setStateAsync(`devices.${id}.cat_${cname}_kg`, { val: cat.weight, ack: true });
}
}
if (data.switches) {
for (const sw of Object.keys(data.switches)) {
const val = !!data.switches[sw];
if (await this.getObjectAsync(`devices.${id}.${sw}`)) {
await this.setStateAsync(`devices.${id}.${sw}`, { val, ack: true });
}
}
}
} catch (err) {
this.log.error(`Polling device ${id} failed: ${err}`);
}
}
}
async onStateChange(id, state) {
if (!state || state.ack) return;
const [_, devId, stateName] = id.split('.').slice(-3);
if (!this.devices[devId]) return;
if (['clean', 'level'].includes(stateName)) {
await this.triggerDeviceAction(devId, stateName);
await this.setStateAsync(id, { val: false, ack: true });
} else {
await this.toggleDeviceSetting(devId, stateName, state.val);
}
}
async triggerDeviceAction(devId, action) {
try {
await axios.post(`${this.baseUrl}/v1/devices/${devId}/action`, { action }, { headers: { Authorization: `Bearer ${this.token}` } });
this.log.info(`Triggered ${action} for device ${devId}`);
} catch (err) {
this.log.error(`Action ${action} failed: ${err}`);
}
}
async toggleDeviceSetting(devId, setting, value) {
try {
await axios.patch(`${this.baseUrl}/v1/devices/${devId}/settings`, { [setting]: value }, { headers: { Authorization: `Bearer ${this.token}` } });
this.log.info(`Toggled ${setting} for ${devId} to ${value}`);
} catch (err) {
this.log.error(`Toggle ${setting} failed: ${err}`);
}
}
_slugify(str) {
return (str || '').toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_|_$/g, '');
}
_coalesce(...args) {
for (const a of args) if (a !== undefined && a !== null) return a;
return undefined;
}
onUnload(callback) {
try {
if (this.pollTimer) clearInterval(this.pollTimer);
callback();
} catch (e) {
callback();
}
}
}
if (require.main !== module) { module.exports = (options) => new Neakasa(options); } else { new Neakasa(); }
/* ADMIN README / Notes: This adapter connects ioBroker to the Neakasa Cloud API. It creates full state trees per device, polls periodically, allows commands and switch toggles. TODO (once raw Python endpoints available):
- Replace placeholder endpoints with real ones from hass-neakasa/api.py
- Implement token refresh (if available)
- Add support for websockets (if Neakasa supports push updates) */ `