Compare commits

...

3 Commits

Author SHA1 Message Date
c97253597b release: v0.9.1-beta4 2026-04-21 22:31:39 +02:00
3ceee973c5 release: v0.9.1-beta3 2026-04-21 22:27:01 +02:00
0c07f70fee release: v0.9.1-beta2 2026-04-21 21:43:28 +02:00
3 changed files with 145 additions and 21 deletions

View File

@@ -1 +1 @@
0.9.1-beta1 0.9.1-beta4

View File

@@ -124,6 +124,10 @@ class KobraXClient:
return (f"anycubic/anycubicCloud/v1/slicer/printer/" return (f"anycubic/anycubicCloud/v1/slicer/printer/"
f"{self.mode_id}/{self.device_id}/{msg_type}") f"{self.mode_id}/{self.device_id}/{msg_type}")
def _web_topic(self, msg_type: str) -> str:
return (f"anycubic/anycubicCloud/v1/web/printer/"
f"{self.mode_id}/{self.device_id}/{msg_type}")
def _sub_topic(self) -> str: def _sub_topic(self) -> str:
return (f"anycubic/anycubicCloud/v1/printer/public/" return (f"anycubic/anycubicCloud/v1/printer/public/"
f"{self.mode_id}/{self.device_id}/#") f"{self.mode_id}/{self.device_id}/#")
@@ -345,6 +349,23 @@ class KobraXClient:
return None return None
return entry["result"] return entry["result"]
def publish_web(self, msg_type: str, action: str, data=None) -> None:
"""Fire-and-forget publish on the web/printer topic (used for runtime updates during print)."""
msgid = str(uuid.uuid4())
payload = json.dumps({
"type": msg_type,
"action": action,
"msgid": msgid,
"timestamp": int(time.time() * 1000),
"data": data,
}, separators=(",", ":"))
topic = self._web_topic(msg_type)
try:
with self._lock:
self._sock.sendall(_build_publish(topic, payload))
except Exception as e:
print(f"[kobrax] web send error: {e}")
# -- High-level commands ------------------------------------------------- # -- High-level commands -------------------------------------------------
def query_info(self) -> dict | None: def query_info(self) -> dict | None:

View File

@@ -82,6 +82,8 @@ class KobraXBridge:
"fan_speed": 0, "fan_speed": 0,
"light_on": False, "light_on": False,
"light_brightness": 80, "light_brightness": 80,
"taskid": "-1",
"print_speed_mode": 2,
} }
self._ams_slots: list[dict] = [] self._ams_slots: list[dict] = []
self._ams_loaded_slot: int = -1 self._ams_loaded_slot: int = -1
@@ -125,6 +127,11 @@ class KobraXBridge:
self._state["curr_layer"] = d["curr_layer"] self._state["curr_layer"] = d["curr_layer"]
if "total_layers" in d: if "total_layers" in d:
self._state["total_layers"] = d["total_layers"] self._state["total_layers"] = d["total_layers"]
if "taskid" in d:
self._state["taskid"] = str(d["taskid"])
settings = d.get("settings") or {}
if "print_speed_mode" in settings:
self._state["print_speed_mode"] = int(settings["print_speed_mode"])
self._push_status_update() self._push_status_update()
def _on_info(self, payload: dict): def _on_info(self, payload: dict):
@@ -149,6 +156,9 @@ class KobraXBridge:
fan = d.get("fan_speed_pct") fan = d.get("fan_speed_pct")
if fan is not None: if fan is not None:
self._state["fan_speed"] = int(fan) self._state["fan_speed"] = int(fan)
speed_mode = d.get("print_speed_mode")
if speed_mode is not None:
self._state["print_speed_mode"] = int(speed_mode)
self._push_status_update() self._push_status_update()
def _on_file(self, payload: dict): def _on_file(self, payload: dict):
@@ -634,6 +644,14 @@ main{flex:1;overflow-y:auto;padding:20px}
.btn-cancel{background:#2d0d0d;color:var(--err);border:1px solid var(--err)} .btn-cancel{background:#2d0d0d;color:var(--err);border:1px solid var(--err)}
.btn-accent{background:var(--accent);color:#001a24} .btn-accent{background:var(--accent);color:#001a24}
.btn-sm{padding:7px 12px;font-size:12px} .btn-sm{padding:7px 12px;font-size:12px}
.spd-btn{flex:1;border:1.5px solid var(--border);background:var(--raised);color:var(--txt);
border-radius:10px;padding:14px 8px;font-size:13px;font-weight:600;cursor:pointer;
transition:all .15s;display:flex;flex-direction:column;align-items:center;gap:4px}
.spd-btn:hover{border-color:var(--accent);color:var(--accent)}
.spd-btn.spd-active{border-color:var(--accent);background:rgba(0,200,255,.12);color:var(--accent)}
.spd-btn .spd-icon{font-size:22px}
.spd-bar{height:4px;border-radius:2px;background:var(--border);margin-top:10px;overflow:hidden}
.spd-bar-fill{height:100%;border-radius:2px;background:linear-gradient(90deg,var(--accent2),var(--accent));transition:width .3s}
/* ── TEMPS ── */ /* ── TEMPS ── */
.temp-pair{display:grid;grid-template-columns:1fr 1fr;gap:12px} .temp-pair{display:grid;grid-template-columns:1fr 1fr;gap:12px}
@@ -884,9 +902,9 @@ nav.bottom-nav{display:none;position:fixed;bottom:0;left:0;right:0;
<!-- Kamera --> <!-- Kamera -->
<div class="card" style="grid-column:1/-1"> <div class="card" style="grid-column:1/-1">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:10px"> <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:10px">
<div class="card-title" style="margin-bottom:0"><span>📷</span> Kamera</div> <div class="card-title" style="margin-bottom:0"><span>📷</span> <span id="d-card-cam">Kamera</span></div>
<div style="display:flex;align-items:center;gap:10px"> <div style="display:flex;align-items:center;gap:10px">
<span style="font-size:12px;color:var(--txt2)">💡 Licht</span> <span id="d-lbl-light" style="font-size:12px;color:var(--txt2)">💡 Licht</span>
<label class="toggle"> <label class="toggle">
<input type="checkbox" id="d-light-toggle" onchange="setLight()"> <input type="checkbox" id="d-light-toggle" onchange="setLight()">
<span class="toggle-track"></span> <span class="toggle-track"></span>
@@ -944,7 +962,7 @@ nav.bottom-nav{display:none;position:fixed;bottom:0;left:0;right:0;
</div> </div>
</div> </div>
<div class="temp-block"> <div class="temp-block">
<div class="temp-label">Bett</div> <div class="temp-label" id="d-lbl-bed">Bett</div>
<div class="temp-row"> <div class="temp-row">
<div class="temp-val" id="d-bt"></div> <div class="temp-val" id="d-bt"></div>
<div class="temp-unit">°C</div> <div class="temp-unit">°C</div>
@@ -1002,6 +1020,28 @@ nav.bottom-nav{display:none;position:fixed;bottom:0;left:0;right:0;
<div style="text-align:center;margin-top:8px;font-size:12px;color:var(--txt2)">Schrittweite: <span id="step-display">1</span> mm</div> <div style="text-align:center;margin-top:8px;font-size:12px;color:var(--txt2)">Schrittweite: <span id="step-display">1</span> mm</div>
</div> </div>
<!-- Print Speed -->
<div class="card">
<div class="card-title"><span>🏎</span> <span id="d-card-speed">Druckgeschwindigkeit</span></div>
<div style="display:flex;gap:8px;margin-top:4px">
<button class="spd-btn" id="d-spd-1" onclick="setSpeed(1)">
<span class="spd-icon">🐢</span>
<span id="d-spd-lbl-1">Leise</span>
</button>
<button class="spd-btn spd-active" id="d-spd-2" onclick="setSpeed(2)">
<span class="spd-icon">⚡</span>
<span id="d-spd-lbl-2">Normal</span>
</button>
<button class="spd-btn" id="d-spd-3" onclick="setSpeed(3)">
<span class="spd-icon">🚀</span>
<span id="d-spd-lbl-3">Sport</span>
</button>
</div>
<div class="spd-bar" style="margin-top:12px">
<div class="spd-bar-fill" id="d-spd-bar" style="width:50%"></div>
</div>
</div>
<!-- Lüfter --> <!-- Lüfter -->
<div class="card"> <div class="card">
<div class="card-title"><span>🌀</span> <span id="d-card-lightfan">Lüfter</span></div> <div class="card-title"><span>🌀</span> <span id="d-card-lightfan">Lüfter</span></div>
@@ -1033,8 +1073,8 @@ nav.bottom-nav{display:none;position:fixed;bottom:0;left:0;right:0;
<span id="ams-slot-label" style="min-width:48px;font-size:13px;font-weight:600">Slot 1</span> <span id="ams-slot-label" style="min-width:48px;font-size:13px;font-weight:600">Slot 1</span>
</div> </div>
<div style="display:flex;gap:10px"> <div style="display:flex;gap:10px">
<button class="btn" style="flex:1" onclick="amsFeed(1)">⬇ Einziehen</button> <button class="btn" style="flex:1" onclick="amsFeed(1)">⬇ <span class="lbl-feed">Einziehen</span></button>
<button id="btn-unload" class="btn" style="flex:1" onclick="amsFeed(2)">⬆ Ausziehen</button> <button id="btn-unload" class="btn" style="flex:1" onclick="amsFeed(2)">⬆ <span class="lbl-unload">Ausziehen</span></button>
</div> </div>
</div> </div>
</div> </div>
@@ -1061,7 +1101,7 @@ nav.bottom-nav{display:none;position:fixed;bottom:0;left:0;right:0;
var S={nozzle_temp:0,nozzle_target:0,bed_temp:0,bed_target:0, var S={nozzle_temp:0,nozzle_target:0,bed_temp:0,bed_target:0,
print_state:'standby',filename:'',progress:0,print_duration:0, print_state:'standby',filename:'',progress:0,print_duration:0,
curr_layer:0,total_layers:0,printer_name:'Kobra X',firmware_version:'', curr_layer:0,total_layers:0,printer_name:'Kobra X',firmware_version:'',
camera_url:'',fan_speed:0,light_on:false,light_brightness:80,ams_slots:[]}; camera_url:'',fan_speed:0,print_speed_mode:2,light_on:false,light_brightness:80,ams_slots:[]};
var tempHistory={n:[],b:[]}; var tempHistory={n:[],b:[]};
var camOn=false; var camOn=false;
var currentStep=1; var currentStep=1;
@@ -1080,7 +1120,9 @@ var LANG_DE={
header_status_standby:'Bereit',header_status_printing:'Druckt',header_status_complete:'Fertig',header_status_error:'Fehler', header_status_standby:'Bereit',header_status_printing:'Druckt',header_status_complete:'Fertig',header_status_error:'Fehler',
kobra_free:'Bereit',kobra_busy:'Beschäftigt',kobra_printing:'Druckt',kobra_preheating:'Aufheizen',kobra_auto_leveling:'Nivellierung',kobra_checking:'Prüfung',kobra_updated:'Aktualisierung',kobra_init:'Initialisierung',kobra_finished:'Abgeschlossen',kobra_failed:'Fehler',kobra_canceled:'Abgebrochen',kobra_offline:'Offline', kobra_free:'Bereit',kobra_busy:'Beschäftigt',kobra_printing:'Druckt',kobra_preheating:'Aufheizen',kobra_auto_leveling:'Nivellierung',kobra_checking:'Prüfung',kobra_updated:'Aktualisierung',kobra_init:'Initialisierung',kobra_finished:'Abgeschlossen',kobra_failed:'Fehler',kobra_canceled:'Abgebrochen',kobra_offline:'Offline',
nav_dashboard:'Dashboard',nav_print:'Druck',nav_temps:'Temperaturen',nav_motion:'Achsen',nav_ams:'AMS',nav_extras:'Licht / Lüfter',nav_console:'Konsole', nav_dashboard:'Dashboard',nav_print:'Druck',nav_temps:'Temperaturen',nav_motion:'Achsen',nav_ams:'AMS',nav_extras:'Licht / Lüfter',nav_console:'Konsole',
card_progress:'Fortschritt',card_temps:'Temperaturen',card_light_fan:'Lüfter', card_progress:'Fortschritt',card_temps:'Temperaturen',card_light_fan:'Lüfter',card_speed:'Druckgeschwindigkeit',card_cam:'Kamera',
speed_silent:'🐢 Leise',speed_normal:'⚡ Normal',speed_sport:'🚀 Sport',
lbl_light:'💡 Licht',lbl_feed:'Einziehen',lbl_unload:'Ausziehen',
cam_placeholder:'📷 Kamera nicht gestartet',btn_cam_start:'▶ Kamera',btn_cam_stop:'◼ Kamera', cam_placeholder:'📷 Kamera nicht gestartet',btn_cam_start:'▶ Kamera',btn_cam_stop:'◼ Kamera',
btn_pause:'⏸ Pause',btn_resume:'▶ Weiter',btn_cancel:'✕ Stopp', btn_pause:'⏸ Pause',btn_resume:'▶ Weiter',btn_cancel:'✕ Stopp',
label_nozzle:'Nozzle',label_bed:'Bett',label_fan:'🌀 Lüfter',label_light:'💡 Licht',label_on_off:'Ein / Aus',label_speed:'Geschwindigkeit', label_nozzle:'Nozzle',label_bed:'Bett',label_fan:'🌀 Lüfter',label_light:'💡 Licht',label_on_off:'Ein / Aus',label_speed:'Geschwindigkeit',
@@ -1103,7 +1145,9 @@ var LANG_EN={
header_status_standby:'Ready',header_status_printing:'Printing',header_status_complete:'Complete',header_status_error:'Error', header_status_standby:'Ready',header_status_printing:'Printing',header_status_complete:'Complete',header_status_error:'Error',
kobra_free:'Ready',kobra_busy:'Busy',kobra_printing:'Printing',kobra_preheating:'Preheating',kobra_auto_leveling:'Auto Leveling',kobra_checking:'Checking',kobra_updated:'Updating',kobra_init:'Initializing',kobra_finished:'Finished',kobra_failed:'Error',kobra_canceled:'Cancelled',kobra_offline:'Offline', kobra_free:'Ready',kobra_busy:'Busy',kobra_printing:'Printing',kobra_preheating:'Preheating',kobra_auto_leveling:'Auto Leveling',kobra_checking:'Checking',kobra_updated:'Updating',kobra_init:'Initializing',kobra_finished:'Finished',kobra_failed:'Error',kobra_canceled:'Cancelled',kobra_offline:'Offline',
nav_dashboard:'Dashboard',nav_print:'Print',nav_temps:'Temperatures',nav_motion:'Motion',nav_ams:'AMS',nav_extras:'Light / Fan',nav_console:'Console', nav_dashboard:'Dashboard',nav_print:'Print',nav_temps:'Temperatures',nav_motion:'Motion',nav_ams:'AMS',nav_extras:'Light / Fan',nav_console:'Console',
card_progress:'Progress',card_temps:'Temperatures',card_light_fan:'Fan', card_progress:'Progress',card_temps:'Temperatures',card_light_fan:'Fan',card_speed:'Print Speed',card_cam:'Camera',
speed_silent:'🐢 Silent',speed_normal:'⚡ Normal',speed_sport:'🚀 Sport',
lbl_light:'💡 Light',lbl_feed:'Load',lbl_unload:'Unload',
cam_placeholder:'📷 Camera not started',btn_cam_start:'▶ Camera',btn_cam_stop:'◼ Camera', cam_placeholder:'📷 Camera not started',btn_cam_start:'▶ Camera',btn_cam_stop:'◼ Camera',
btn_pause:'⏸ Pause',btn_resume:'▶ Resume',btn_cancel:'✕ Stop', btn_pause:'⏸ Pause',btn_resume:'▶ Resume',btn_cancel:'✕ Stop',
label_nozzle:'Nozzle',label_bed:'Bed',label_fan:'🌀 Fan',label_light:'💡 Light',label_on_off:'On / Off',label_speed:'Speed', label_nozzle:'Nozzle',label_bed:'Bed',label_fan:'🌀 Fan',label_light:'💡 Light',label_on_off:'On / Off',label_speed:'Speed',
@@ -1143,7 +1187,11 @@ function applyLang(){
setText('d-card-progress',T.card_progress); setText('d-card-progress',T.card_progress);
setText('d-card-temps',T.card_temps); setText('d-card-temps',T.card_temps);
setText('d-card-lightfan',T.card_light_fan); setText('d-card-lightfan',T.card_light_fan);
setText('d-card-speed',T.card_speed);
setText('d-card-cam',T.card_cam);
setText('d-card-ams',T.panel_ams_title); setText('d-card-ams',T.panel_ams_title);
setText('d-lbl-light',T.lbl_light);
setText('d-lbl-bed',T.label_bed);
// Dashboard buttons // Dashboard buttons
setText('d-btn-pause',T.btn_pause); setText('d-btn-pause',T.btn_pause);
setText('d-btn-resume',T.btn_resume); setText('d-btn-resume',T.btn_resume);
@@ -1178,6 +1226,13 @@ function applyLang(){
setText('lbl-mode-id',T.settings_mode_id); setText('lbl-mode-id',T.settings_mode_id);
setText('lbl-update-check',T.update_check); setText('lbl-update-check',T.update_check);
setText('lbl-update-apply',T.update_apply); setText('lbl-update-apply',T.update_apply);
// Speed buttons
setText('d-spd-lbl-1',T.speed_silent.replace(/^\S+\s/,''));
setText('d-spd-lbl-2',T.speed_normal.replace(/^\S+\s/,''));
setText('d-spd-lbl-3',T.speed_sport.replace(/^\S+\s/,''));
// AMS feed/unload
document.querySelectorAll('.lbl-feed').forEach(e=>e.textContent=T.lbl_feed);
document.querySelectorAll('.lbl-unload').forEach(e=>e.textContent=T.lbl_unload);
} }
function setText(id,txt){var el=document.getElementById(id);if(el)el.textContent=txt;} function setText(id,txt){var el=document.getElementById(id);if(el)el.textContent=txt;}
(function(){ (function(){
@@ -1268,6 +1323,15 @@ function applyState(){
var dfan=document.getElementById('d-fan');if(dfan)dfan.value=s.fan_speed; var dfan=document.getElementById('d-fan');if(dfan)dfan.value=s.fan_speed;
var dfanval=document.getElementById('d-fan-val');if(dfanval)dfanval.textContent=s.fan_speed; var dfanval=document.getElementById('d-fan-val');if(dfanval)dfanval.textContent=s.fan_speed;
// speed mode buttons
var spdWidths={1:25,2:55,3:90};
[1,2,3].forEach(function(m){
var b=document.getElementById('d-spd-'+m);
if(b) b.classList.toggle('spd-active', s.print_speed_mode===m);
});
var spdBar=document.getElementById('d-spd-bar');
if(spdBar) spdBar.style.width=(spdWidths[s.print_speed_mode]||55)+'%';
// AMS // AMS
if(s.ams_slots&&s.ams_slots.length){ if(s.ams_slots&&s.ams_slots.length){
var html=''; var html='';
@@ -1475,7 +1539,7 @@ function setNozzle(){
function setBed(){ function setBed(){
var v=parseFloat(document.getElementById('p-bed-inp').value||0); var v=parseFloat(document.getElementById('p-bed-inp').value||0);
post('/api/temperature',{nozzle:S.nozzle_target,bed:v}) post('/api/temperature',{nozzle:S.nozzle_target,bed:v})
.then(function(){clog('Bett'+v+'°C','msg-ok')}) .then(function(){clog(T.label_bed+''+v+'°C','msg-ok')})
.catch(function(e){clog('Temp-Fehler: '+e,'msg-err')}); .catch(function(e){clog('Temp-Fehler: '+e,'msg-err')});
} }
@@ -1487,6 +1551,17 @@ function setLight(){
.catch(function(e){clog('Licht-Fehler: '+e,'msg-err')}); .catch(function(e){clog('Licht-Fehler: '+e,'msg-err')});
} }
// ── Print Speed ──
function setSpeed(mode){
S.print_speed_mode=mode;
[1,2,3].forEach(function(m){
var b=document.getElementById('d-spd-'+m);
if(b) b.classList.toggle('spd-active',m===mode);
});
post('/api/speed',{mode:mode})
.catch(function(e){clog('Speed-Fehler: '+e,'msg-err')});
}
// ── Fan ── // ── Fan ──
function setFan(){ function setFan(){
var v=parseInt(document.getElementById('d-fan').value); var v=parseInt(document.getElementById('d-fan').value);
@@ -1507,7 +1582,7 @@ function quickFan(v){
function amsFeed(type){ function amsFeed(type){
var slot=parseInt(document.getElementById('ams-slot-sel').value); var slot=parseInt(document.getElementById('ams-slot-sel').value);
post('/api/ams/feed',{slot_index:slot,type:type}) post('/api/ams/feed',{slot_index:slot,type:type})
.then(function(){clog((type===1?'Einziehen':'Ausziehen')+' Slot '+(slot+1),'msg-ok')}) .then(function(){clog((type===1?T.lbl_feed:T.lbl_unload)+' Slot '+(slot+1),'msg-ok')})
.catch(function(e){clog('AMS-Fehler: '+e,'msg-err')}); .catch(function(e){clog('AMS-Fehler: '+e,'msg-err')});
} }
@@ -1597,6 +1672,21 @@ function toggleCam(){if(camOn)camStop();else camStart()}
self._state["fan_speed"] = speed self._state["fan_speed"] = speed
return web.json_response({"result": "ok"}) return web.json_response({"result": "ok"})
async def handle_api_speed(self, request):
try:
body = await request.json()
except Exception:
body = {}
mode = int(body.get("mode", 2))
loop = asyncio.get_event_loop()
taskid = self._state.get("taskid", "-1")
await loop.run_in_executor(None, lambda: self.client.publish_web(
"print", "update",
{"taskid": taskid, "settings": {"print_speed_mode": mode}},
))
self._state["print_speed_mode"] = mode
return web.json_response({"result": "ok"})
async def handle_api_ams_feed(self, request): async def handle_api_ams_feed(self, request):
try: try:
body = await request.json() body = await request.json()
@@ -1642,18 +1732,29 @@ function toggleCam(){if(camOn)camStop();else camStart()}
nozzle = body.get("nozzle") nozzle = body.get("nozzle")
bed = body.get("bed") bed = body.get("bed")
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
printing = self._state.get("print_state") == "printing"
if printing:
# During print: runtime update via web/printer topic, one setting at a time
taskid = self._state.get("taskid", "-1")
if nozzle is not None: if nozzle is not None:
n = int(float(nozzle)) n = int(float(nozzle))
await loop.run_in_executor(None, lambda: self.client.publish( await loop.run_in_executor(None, lambda: self.client.publish_web(
"tempature", "set", "print", "update",
{"type": 0, "target_nozzle_temp": n, "target_hotbed_temp": 0}, {"taskid": taskid, "settings": {"target_nozzle_temp": n}},
timeout=0
)) ))
if bed is not None: if bed is not None:
b = int(float(bed)) b = int(float(bed))
await loop.run_in_executor(None, lambda: self.client.publish_web(
"print", "update",
{"taskid": taskid, "settings": {"target_hotbed_temp": b}},
))
else:
# Idle: standard tempature/set with both values
n = int(float(nozzle)) if nozzle is not None else int(self._state["nozzle_target"])
b = int(float(bed)) if bed is not None else int(self._state["bed_target"])
await loop.run_in_executor(None, lambda: self.client.publish( await loop.run_in_executor(None, lambda: self.client.publish(
"tempature", "set", "tempature", "set",
{"type": 1, "target_hotbed_temp": b, "target_nozzle_temp": 0}, {"target_nozzle_temp": n, "target_hotbed_temp": b},
timeout=0 timeout=0
)) ))
return web.json_response({"result": "ok"}) return web.json_response({"result": "ok"})
@@ -1785,6 +1886,7 @@ function toggleCam(){if(camOn)camStop();else camStart()}
"filename": s["filename"], "filename": s["filename"],
"camera_url": s["camera_url"], "camera_url": s["camera_url"],
"fan_speed": s["fan_speed"], "fan_speed": s["fan_speed"],
"print_speed_mode": s["print_speed_mode"],
"light_on": s["light_on"], "light_on": s["light_on"],
"light_brightness": s["light_brightness"], "light_brightness": s["light_brightness"],
"ams_slots": self._ams_slots, "ams_slots": self._ams_slots,
@@ -2241,6 +2343,7 @@ def build_app(bridge: KobraXBridge) -> web.Application:
# New API endpoints # New API endpoints
r.add_post("/api/light", bridge.handle_api_light) r.add_post("/api/light", bridge.handle_api_light)
r.add_post("/api/fan", bridge.handle_api_fan) r.add_post("/api/fan", bridge.handle_api_fan)
r.add_post("/api/speed", bridge.handle_api_speed)
r.add_post("/api/ams/feed", bridge.handle_api_ams_feed) r.add_post("/api/ams/feed", bridge.handle_api_ams_feed)
r.add_post("/api/axis", bridge.handle_api_axis) r.add_post("/api/axis", bridge.handle_api_axis)
r.add_post("/api/temperature", bridge.handle_api_temperature) r.add_post("/api/temperature", bridge.handle_api_temperature)