Guten Abend Udo ist's noch Mal
Zur Ergänzung die Schaltung für die Beschattungs Anlage
als Bild eingefügt.
Hoffe das erklärt alles.
Für Leute die so etwas brauchen.
Guten Abend Udo ist's noch Mal
Zur Ergänzung die Schaltung für die Beschattungs Anlage
als Bild eingefügt.
Hoffe das erklärt alles.
Für Leute die so etwas brauchen.
Reparaturversuch01.pngMoin Moin
Hab mir mal mein defektes Shell 2PM vorgenommen.
Das Ergebnis ist ernüchternd ; kommt ein meine
Leichensammlung !!!
Als Anhang die Details:
Guten Abend und herzlichen Dank für die Tipps.
Bin im laufe der letzten Stunden auf den Thread :
Shelly Plus 2PM: Teardown gestossen .
Das bringt mich schon ein Stück weiter.
Ja gute Augen , oder grosse Vergrösserungs-
Linse helfen da ein wenig. Arbeite da zum Teil mit
einer USB-Camera und Vergrösserung.
Die nächsten Tage werden es zeigen .
Danke nochmal für die schnelle Antwort.
Der Udo
Guten Abend
Habe das defekte Teil nicht weggeworfen und frage mich,
Kann man da noch etwas retten ?
Wer hat da einen Schaltplan von dem Ding ?
Würde , wenn ich das richtig verstehe erst mal nur im
12/24v DC Betrieb einen Versuch starten.
Kann mir da eventuell der Spezialist thgoebel
einen Tip geben.
Bin für Vorschläge offen
Udo
" Geht nicht Gibts nicht "
Moin Moin
Entschuldigung , hab das ganz übersehen und den Code einfach angehängt.
Mach ich gerne:
Zur Ergänzung; Bin noch an einer Verbesserung dran.
Zur Zeit fehlt da noch eine Hyserese für die Wind-Steuerung.
Muß ich aber erst noch testen und zur Zeit ist da zu wenig Sonne !!!
Wind währe ja genug .
Hier nochmal und hoffe richtig.
/*
Versuch die Beschattung der Roehren-Kollecktoren per Script zu steuern. Rev 3.0 UB
Das läuft soweit seit 6.11.2023
In der Config:
temp_min = minimale Innentemperatur(z.B. meine 35°C)
id_inside = Id des Innentemperaturfühlers (Solar_Kollektor_Ruecklauf_Temp)
rollo == auf 100% (voll abgedeckt) ; auf 0% ( voll eingefahren)
interval = Messen, Auswerten und Schalten alle z.B 10 Sekunden
Für unteren code:
1a. roller/0?go= 4% bei inside-Temp <= 25 Grad
2a. roller/0?go=90% bei outside-Temp >= 45 Grad
3. roller/0?go= 4% wenn Spannung >= 4 V am Schelly Add-On Spannungeingang anliegen ===> zu viel Wind ( Messung mit Flügelrad )
4. roller/0?go= 0% wenn Uhrzeit >= 18:00 ( Abends ist zu wenig Strahlung am Kollektor
*/
function TsKonverter(ts, timeZone, yOff) {
let a =[ts,timeZone,yOff],k =true,b =86400,d =0,e =0,f =0,g =0,h =0,j =[31,28,31,30,31,30,31,31,30,31,30,31];
function fa(va,vb,vg,vd){return va +Math.floor((vb -vg) /vd);}
function fb(ve,vf,vg){for (let y =ve; y <vf; y++){if ((y %4 === 0 && y %100 !== 0) || y %400 === 0)vg +=1;}return vg;}
if (typeof(a[2]) === 'undefined')a[2] =1970;
for (let i in a){let t =typeof(a[i]); if (t === 'undefined' || (t !== 'number' && t !== 'bigint')){print("Error_TsKonverter, Input",i, " wrong Datatype: ",t); a[i] =0; k =false;}}
g =Math.floor(a[0] /b); f =fa(a[2],g,0,366); d =fb(a[2],f,0); f =fa(a[2],g,d,365); d =fb(a[2],f,0); f =fa(a[2],g,d,365);
if ((f %4 === 0 && f %100 !== 0) || f %400 === 0)j[1] =29;
h =(g -d) %365;
for (let i =0; i <j.length; i++){if (h >j[i]){h -=j[i];}else {e =i +1; if (a[2] === 0) e -=1; break;}}
if (a[2] >0)h +=1;
let values ={aa:h,ab:e,ac:f,ad:Math.floor((a[0] %b) /3600) +a[1],ae:Math.floor((a[0] %3600) /60),af:Math.floor(a[0] %60)};
if (values.ad === 24){values.aa +=1; values.ad =0;}
let number ={Tag:values.aa,Monat:values.ab,Jahr:values.ac,Stunde:values.ad,Minute:values.ae,Sekunde:values.af};
let string ={Tag:JSON.stringify(values.aa),Monat:JSON.stringify(values.ab),Jahr:JSON.stringify(values.ac),Stunde:JSON.stringify(values.ad),Minute:JSON.stringify(values.ae),Sekunde:JSON.stringify(values.af)};
let sa ={Tag:JSON.stringify(values.aa),Monat:JSON.stringify(values.ab),Jahr:JSON.stringify(values.ac),Stunde:JSON.stringify(values.ad),Minute:JSON.stringify(values.ae),Sekunde:JSON.stringify(values.af)};
string.Datum =(string.Tag +':' +string.Monat +':' +string.Jahr);
string.Zeit =(string.Stunde +':' +string.Minute +':' +string.Sekunde);
for (let v in sa){if (sa[v].length < 2){sa[v] ='0' +sa[v];}}
let twoDigits ={Tag:sa.Tag,Monat:sa.Monat,Jahr:sa.Jahr,Stunde:sa.Stunde,Minute:sa.Minute,Sekunde:sa.Sekunde};
twoDigits.Datum =(twoDigits.Tag +':' +twoDigits.Monat +':' +twoDigits.Jahr);
twoDigits.Zeit =(twoDigits.Stunde +':' +twoDigits.Minute +':' +twoDigits.Sekunde);
let ausgabe ={Valid:k,Number:number,String:string,TwoDigits:twoDigits};
return ausgabe;
}
//############################## Config ############################
let Config = {
temp_min : 25, // Minimale Temperatur
temp_max : 45, // Maximale Kollektor Temperatur
id_inside : 100, // Sensor Id (Solar_Kollektor_Ruecklauf_Temp)
id_input : 100, // Wind Sensor 0 - 100%
W_max : 30, // Zu viel Wind
speed_real : 0, // Wahrer Wind
w_speed : 0,
w_temp : 0,
roller : 0,
Abend : 728, //18h==>1080 + 45 minuten == 1125
interval : 10 // 2 seconds is the minimum interval
}
//############################## Config end ############################
function watch()
{
try
{
let shellyTime = Shelly.getComponentStatus("sys").unixtime;
let newDate = TsKonverter(shellyTime, +1);
if(newDate.Valid)
{
print(newDate.TwoDigits.Zeit);
// h:m in Minuten umrechnen
let h =(newDate.Number.Stunde);
let m = (newDate.Number.Minute);
let minuten = (( h * 60 ) + m );
print('Minuten = ',minuten);
}
//else {
temp_inside = Shelly.getComponentStatus('Temperature', Config.id_inside).tC; //temperature:100":{"id": 100,"tC":20.9, "tF":69.6}
print('Temp. Kollektor Ruecklauf =',temp_inside); // (Solar_Kollektor_Ruecklauf_Temp)
let w_temp = Math.floor(temp_inside);
print ('Kollektor Ruecklauf_integer =' ,w_temp);
//----------------- das Läuft ------------------------------------------------
speed_real = Shelly.getComponentStatus('input',Config.id_input).percent; //"input:100":{"id":100,"percent":0.0}
print('Wind_real =', speed_real);
let w_speed = Math.floor(speed_real);
print ('Wind_integer =' ,w_speed);
// --------------- Info wird ausgegeben ------------------------------------------------
if (minuten >= Config. Abend) {
Shelly.call("http.get",{url:'http://127.0.0.1/roller/' + JSON.stringify(Config.roller) + '?go=to_pos&roller_pos=4'});
print('Rollo(',Config.roller, ') Rollo geschlossen Abend >= ', Config.minuten);
}
else {
// tu nix
if (w_speed >= Config. W_max) {
Shelly.call("http.get",{url:'http://127.0.0.1/roller/' + JSON.stringify(Config.roller) + '?go=to_pos&roller_pos=4'});
print('Rollo(',Config.roller, ') geschlossen. Wind_speed to high >= ', w_speed);
} //
else { //
if (w_temp <= Config.temp_min) {
Shelly.call("http.get",{url:'http://127.0.0.1/roller/' + JSON.stringify(Config.roller) + '?go=to_pos&roller_pos=4'});
print('Rollo(',Config.roller, ') geschlossen. Inside temp <= ', Config.temp_min);
}
else {
if (w_temp >= Config.temp_max) {
Shelly.call("http.get",{url:'http://127.0.0.1/roller/' + JSON.stringify(Config.roller) + '?go=to_pos&roller_pos=90'});
print('Rollo(',Config.roller, ') beschattet. Outside temp >= ', Config.temp_max);
}
else {
}
}
} //
//}
}
}
//}
// print('\n')
catch (err) {
Shelly.call("http.get", {192.168.178.57 + JSON.stringify(Config.rollo) +'?go=to_pos&roller_pos=90'});
print(err);
}
}
function start() {
// do a first shot
watch();
// set watch timer to configured value
Timer.set(Config.interval * 1000,true,watch);
}
// schedule script start for 1 second
Timer.set(1000,false,start); // alle 10 Secunden prüfen & reagieren
Alles anzeigen
UBrollo kannst du Deinen Beitrag nochmals Editieren und dein Script in den Code Tag einfügen? Dadurch wird er besser ersichtlich und lesbar.
Guten Tag
Mein Name Udo , bin noch recht neu hier im Forum
und möchte über mein neues Projekt schreiben.
"Röhren-Kollektor-Beschattungs-Projekt"
? Wer braucht denn sowas und warum ?
Vor 21 Jahren wurde ein Zweifamilienhaus für 6 Personen
mit Fußbodenheizung und Solarer Thermieunterstützung geplant.
Der Betrieb in den vergangen Jahren zeigte, daß sich der Mehraufwand
bei der Installation bezahlt hat.
Nur hat zu Beginn keiner mit unserem nun spührbaren Umweltveränderungen
gerechnet .
Es wurden 3 x Paradigma CPC20 Röhren Kolletoren verbaut,
welche in meinem Fall, (PLZ88271) das gesamte Haus von April bis
in den späten Oktober ausschließlich mit "Solarwärme" mit Warmwasser
und zeitweise mit Heizung versorgt.
Die 2 Enkel-Kinder ( ein maßgeblicher Warmwasser Verbraucher)
sind flüge und ausgeflogen, das heißt weniger Warmwasserverbrauch
plus seigende Sommertemperaturen treiben die Thermianlage in die Stagnation.
Das ist absolut nicht gut und schädigt die Anlagenstruktur.
Das ist also der Grund des Projekts das diesen Sommer bereits
seine Funktion bewiesen hat.
Das ganze aber mit manueller Bedienung.
Die Hälfte der Röhren-Kollektoren werden mit einer quasi Markise beschattet
und verhindern die Stagnation der "Thermie - Solar -Anlage".
Stagnation ==> die gesammte Transportflüssigkeit ( Kühlmittel) wird in den
Ausgleichsbehälter im Keller gedrückt und bei abkühlen entstehen Gasblasen
im System . Passiert das öfter findet auf Grund der Gasblasen kein Umwälzung
mehr statt und das Sytem ist ausgefallen.
Soweit die Vorgeschichte; Hoffe ist nicht zu lang ausgefallen .
Vor geraumer Zeit bin ich durch ein Youtube video
"Wie geht die Scripting-Funktion der neuen Shelly PRO
und Shelly PLUS und was kann man damit machen?";
auf die Möglichkeit gestossen meine sehr geringen Erfahrungen
mit Shelly-Produkten zu erweitern.
So bin ich dann auch, auf dieses sehr interessante und gute
Forum gestossen.
Bisher habe ich den Shelly 2.5 als "switch ( Beleuchtung) & auch als cover"
an einer Markise eingesetzt. Die Anlagen laufen problemlos seit ca 2 Jahren.
Die Markisensteuerung unter Anwendung des Shelly 2PM brachten mich
bei meinem " Röhren-Kollektor-Beschattungs-Projekt " auf die Idee,
die deutlich anspruchsvollere Steuerung der Anlage damit duchzuführen.
In der Zwischenzeit wurde das ADD-ON für den 2PM eingeführt und so
erübrigte das den Einsatz eines Arduino für die Meßwerterfassung.
Das Pflichtenheft:
1. Keine Beschattung bei: T-Sensor Temp <= 25 Grad
2. Volle Beschattung bei: T-Sensor Temp >= 75 Grad
3. Entfernen der Beschattung : wenn Spannung >= 4 V = & > 4 Bf Wind
am Schelly Add-On Spannungeingang anliegen
===> zu viel Wind ( Messung mit Flügelrad )
4. Beschattung nicht notwendig wenn Uhrzeit >= 18:00
( Abends ist zu wenig Strahlung am Kollektor
5. Anlage in den Winterschlaf legen (Okt. Bis Mai.) manuel per Netzschalter
Meine Programmierkenntnisse sind sehr rudimentär in C , Basic & Arduino
und so nahm ich mir Zeit die Grundlagen für Java Script zu studieren
und die Zahlreichen Muster-Scripts in diesem Forum zu analysieren.
Mit reinem copy & paste ist man in dieser Sache aber sehr schnell am
Ende und die Reihe der Error's wird immer länger.
In diesem Fall hilft nur Beharrlichkeit und so blieb ich meinem
Wahlspruch treu !!! " Geht nicht gibts nicht "
Die Hardware:
1 Stck. Markise 3,5m x 1,6m Rohrmotor mit Endschaltern justierbar
1 Stck Shelly ADD-ON
1 Stck Shelly 2PM
1 Stück Steckernetzteil 220AC/12v 1A DC Schaltnetzteil
(Spannungsversorgung des Flügelrads)
1 Stck Flügelrad (https://de.aliexpress.com/item/1005005500304078.html)
hier die 0-10V Version verwenden
1 Stck Temeratursensor DS18b20
Herausgekommen ist dabei ein scetch der für meine Ansprüche funktioniert!
Echte Programmierer werde sagen das geht besser ( ich kann es nicht )
Für all diejenigen die etwas ähnliches brauchen können hier ohne Gewähr
und nur für den eigenen Bedarf !!!
der Code.
Der Name: Rollo_Temp_Wind_Time
/*
Versuch die Beschattung der Roehren-Kollecktoren per Script zu steuern. Rev 3.0 UB
Das läuft soweit seit 6.11.2023
In der Config:
temp_min = minimale Innentemperatur(z.B. meine 35°C)
id_inside = Id des Innentemperaturfühlers (Solar_Kollektor_Ruecklauf_Temp)
rollo == auf 100% (voll abgedeckt) ; auf 0% ( voll eingefahren)
interval = Messen, Auswerten und Schalten alle z.B 10 Sekunden
Für unteren code:
1a. roller/0?go= 4% bei inside-Temp <= 25 Grad // das waren Werte zu testen
2a. roller/0?go=90% bei outside-Temp >= 45 Grad
3. roller/0?go= 4% wenn Spannung >= 4 V am Schelly Add-On Spannungeingang anliegen ===> zu viel Wind ( Messung mit Flügelrad )
4. roller/0?go= 0% wenn Uhrzeit >= 18:00 ( Abends ist zu wenig Strahlung am Kollektor
*/
function TsKonverter(ts, timeZone, yOff) {
let a =[ts,timeZone,yOff],k =true,b =86400,d =0,e =0,f =0,g =0,h =0,j =[31,28,31,30,31,30,31,31,30,31,30,31];
function fa(va,vb,vg,vd){return va +Math.floor((vb -vg) /vd);}
function fb(ve,vf,vg){for (let y =ve; y <vf; y++){if ((y %4 === 0 && y %100 !== 0) y %400 === 0)vg +=1;}return vg;}
if (typeof(a[2]) === 'undefined')a[2] =1970;
for (let i in a){let t =typeof(a[i]); if (t === 'undefined' (t !== 'number' && t !== 'bigint')){print("Error_TsKonverter, Input",i, " wrong Datatype: ",t); a[i] =0; k =false;}}
g =Math.floor(a[0] /b); f =fa(a[2],g,0,366); d =fb(a[2],f,0); f =fa(a[2],g,d,365); d =fb(a[2],f,0); f =fa(a[2],g,d,365);
if ((f %4 === 0 && f %100 !== 0) f %400 === 0)j[1] =29;
h =(g -d) %365;
for (let i =0; i <j.length; i++){if (h >j[i]){h -=j[i];}else {e =i +1; if (a[2] === 0) e -=1; break;}}
if (a[2] >0)h +=1;
let values ={aa:h,ab:e,ac:f,ad:Math.floor((a[0] %b) /3600) +a[1],ae:Math.floor((a[0] %3600) /60),af:Math.floor(a[0] %60)};
if (values.ad === 24){values.aa +=1; values.ad =0;}
let number ={Tag:values.aa,Monat:values.ab,Jahr:values.ac,Stunde:values.ad,Minute:values.ae,Sekunde:values.af};
let string ={Tag:JSON.stringify(values.aa),Monat:JSON.stringify(values.ab),Jahr:JSON.stringify(values.ac),Stunde:JSON.stringify(values.ad),Minute:JSON.stringify(values.ae),Sekunde:JSON.stringify(values.af)};
let sa ={Tag:JSON.stringify(values.aa),Monat:JSON.stringify(values.ab),Jahr:JSON.stringify(values.ac),Stunde:JSON.stringify(values.ad),Minute:JSON.stringify(values.ae),Sekunde:JSON.stringify(values.af)};
string.Datum =(string.Tag +':' +string.Monat +':' +string.Jahr);
string.Zeit =(string.Stunde +':' +string.Minute +':' +string.Sekunde);
for (let v in sa){if (sa[v].length < 2){sa[v] ='0' +sa[v];}}
let twoDigits ={Tag:sa.Tag,Monat:sa.Monat,Jahr:sa.Jahr,Stunde:sa.Stunde,Minute:sa.Minute,Sekunde:sa.Sekunde};
twoDigits.Datum =(twoDigits.Tag +':' +twoDigits.Monat +':' +twoDigits.Jahr);
twoDigits.Zeit =(twoDigits.Stunde +':' +twoDigits.Minute +':' +twoDigits.Sekunde);
let ausgabe ={Valid:k,Number:number,String:string,TwoDigits:twoDigits};
return ausgabe;
}
//############################## Config ############################
let Config = {
temp_min : 25, // Minimale Temperatur Testwerte
temp_max : 45, // Maximale Kollektor Temperatur
id_inside : 100, // Sensor Id (Solar_Kollektor_Ruecklauf_Temp)
id_input : 100, // Wind Sensor 0 - 100%
W_max : 40, // Zu viel Wind
speed_real : 0, // Wahrer Wind
w_speed : 0,
w_temp : 0,
roller : 0,
Abend : 728, //18h==>1080 + 45 minuten == 1125
interval : 10 // 2 seconds is the minimum interval
}
//############################## Config end ############################
function watch()
{
try
{
let shellyTime = Shelly.getComponentStatus("sys").unixtime;
let newDate = TsKonverter(shellyTime, +1);
if(newDate.Valid)
{
print(newDate.TwoDigits.Zeit);
// h:m in Minuten umrechnen
let h =(newDate.Number.Stunde);
let m = (newDate.Number.Minute);
let minuten = (( h * 60 ) + m );
print('Minuten = ',minuten);
}
//else {
temp_inside = Shelly.getComponentStatus('Temperature', Config.id_inside).tC; //temperature:100":{"id": 100,"tC":20.9, "tF":69.6}
print('Temp. Kollektor Ruecklauf =',temp_inside); // (Solar_Kollektor_Ruecklauf_Temp)
let w_temp = Math.floor(temp_inside);
print ('Kollektor Ruecklauf_integer =' ,w_temp);
//----------------- das Läuft ------------------------------------------------
speed_real = Shelly.getComponentStatus('input',Config.id_input).percent; //"input:100":{"id":100,"percent":0.0}
print('Wind_real =', speed_real);
let w_speed = Math.floor(speed_real);
print ('Wind_integer =' ,w_speed);
// --------------- Info wird ausgegeben ------------------------------------------------
if (minuten >= Config. Abend) {
Shelly.call("http.get",{url:'http://127.0.0.1/roller/' + JSON.stringify(Config.roller) + '?go=to_pos&roller_pos=4'});
print('Rollo(',Config.roller, ') Rollo geschlossen Abend >= ', Config.minuten);
}
else {
// tu nix
if (w_speed >= Config. W_max) {
Shelly.call("http.get",{url:'http://127.0.0.1/roller/' + JSON.stringify(Config.roller) + '?go=to_pos&roller_pos=4'});
print('Rollo(',Config.roller, ') geschlossen. Wind_speed to high >= ', w_speed);
} //
else { //
if (w_temp <= Config.temp_min) {
Shelly.call("http.get",{url:'http://127.0.0.1/roller/' + JSON.stringify(Config.roller) + '?go=to_pos&roller_pos=4'});
print('Rollo(',Config.roller, ') geschlossen. Inside temp <= ', Config.temp_min);
}
else {
if (w_temp >= Config.temp_max) {
Shelly.call("http.get",{url:'http://127.0.0.1/roller/' + JSON.stringify(Config.roller) + '?go=to_pos&roller_pos=90'});
print('Rollo(',Config.roller, ') beschattet. Outside temp >= ', Config.temp_max);
}
else {
}
}
}
//}
}
}
// print('\n')
catch (err) {
Shelly.call("http.get", {192.168.178.57 + JSON.stringify(Config.rollo) +'?go=to_pos&roller_pos=90'});
print(err);
}
}
function start() {
// do a first shot
watch();
// set watch timer to configured value
Timer.set(Config.interval * 1000,true,watch);
}
// schedule script start for 1 second
Timer.set(1000,false,start); // alle 10 Secunden prüfen & reagieren
Das war es dann , viel Spaß beim testen .
Bin gespannt auf die Reaktionen
Der Udo
Hallo Shelly-Freunde ich bin es nochmal
versuche mal einige Bilder hochzuladen .2PM-tot-01-klein.jpg Oberseit-klein.jpgUnterseite-klein.jpg
Hallo Shelly-Freunde
Bin noch recht neu hier im Forum und möchte von einem weiteren Fall berichten.
Benutze seit einiger Zeit 2 Stck Shelly 2.5 ; einen als Schalter für zwei Leuchtmittel und
den Zweiten um eine Markiese zu steuern. Diese funktionieren so wie man sich das wünscht.
Der neu angeschaffte Selly 2PM sollte für eine Verdunkelung eingesetzt werden.
Die Installation inklusive eines ADD-ON mit 2x DS18B20 verlief ohne Probleme .
Die Steuerung Rollo wurde mit Kalibrieren und diversen Auf- und Abbewegungen getestet und ließ sich mit den angeschlossenen Tastern und der Handy-App steuern.
Die Stromversorgung liegt nun in der Endmontage auf einer Phase zusammen mit einer
Starter gesteuerten 65W Leuchtstofflampe. Am Ende der Manipulationen ( Dosen verklemmen und saubermachen) schalte ich die Leutstofflampe aus !!! ein leiser Knall in der Schelly-Anschlußdose
Und der Sicherungsaotomat des Stromzweigs schaltet ab.
Was ist passiert ??? Automat wieder an , Leuchtstofflampe "EIN" Lampe brennt ; aber der Shelly ist tot keine URL !!
Die Shelly-Dose geöffnet , es riecht nicht gut !! Schmauchspuren verheißen nichts gutes . Ich öffne das Schelly-Geäuse ........ das war es dann wohl .... Durchgebrannte Fuse
ein IC mit deutlicher Beule.
Hier hat augenscheinlich das Abschalten der alten Leuchtstofflampe ( Induktive Drossel) den 2PM getriggerrt und beide Relais angesprochen !!!
Eine kleine Hoffnung hab ich dennoch für das ADD-ON !
ADD-ON an einen für Tests vorgesehenen 1PM angestöpselt , angemeldet und man glaubt es kaum das ADD-ON zeigt die beiden Temperaturfühler und auch dei Messwerte .
!!! Die Hoffnung stirbt zuletzt !!!
Der Schelly " NEU" muß an der Leitung bleiben so mein Entschluß ---- die alte Leuchte muß weichen , keine weiteren Versuche mit Snubber und Co .
Da gibt es eine LED-Lampe und gut ist's.
Mein Resume: Shelly und Induktivitöten ( "ö"ist beabsichtigt) an einer Phase ohne gute Entstörung geht nicht gut .
Schönen Abend noch , ein Schelly-schüler Namens Udo