diff --git a/components/rest_api/webfolder/assets/index-19gq1t3T.js b/components/rest_api/webfolder/assets/index-19gq1t3T.js new file mode 100644 index 0000000..35c2520 --- /dev/null +++ b/components/rest_api/webfolder/assets/index-19gq1t3T.js @@ -0,0 +1,51 @@ +(function(){const d=document.createElement("link").relList;if(d&&d.supports&&d.supports("modulepreload"))return;for(const h of document.querySelectorAll('link[rel="modulepreload"]'))s(h);new MutationObserver(h=>{for(const y of h)if(y.type==="childList")for(const v of y.addedNodes)v.tagName==="LINK"&&v.rel==="modulepreload"&&s(v)}).observe(document,{childList:!0,subtree:!0});function o(h){const y={};return h.integrity&&(y.integrity=h.integrity),h.referrerPolicy&&(y.referrerPolicy=h.referrerPolicy),h.crossOrigin==="use-credentials"?y.credentials="include":h.crossOrigin==="anonymous"?y.credentials="omit":y.credentials="same-origin",y}function s(h){if(h.ep)return;h.ep=!0;const y=o(h);fetch(h.href,y)}})();function ch(i){return i&&i.__esModule&&Object.prototype.hasOwnProperty.call(i,"default")?i.default:i}var Dr={exports:{}},Hn={};/** + * @license React + * react-jsx-runtime.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var Gd;function K0(){if(Gd)return Hn;Gd=1;var i=Symbol.for("react.transitional.element"),d=Symbol.for("react.fragment");function o(s,h,y){var v=null;if(y!==void 0&&(v=""+y),h.key!==void 0&&(v=""+h.key),"key"in h){y={};for(var A in h)A!=="key"&&(y[A]=h[A])}else y=h;return h=y.ref,{$$typeof:i,type:s,key:v,ref:h!==void 0?h:null,props:y}}return Hn.Fragment=d,Hn.jsx=o,Hn.jsxs=o,Hn}var Xd;function J0(){return Xd||(Xd=1,Dr.exports=K0()),Dr.exports}var r=J0(),Mr={exports:{}},ne={};/** + * @license React + * react.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var Qd;function k0(){if(Qd)return ne;Qd=1;var i=Symbol.for("react.transitional.element"),d=Symbol.for("react.portal"),o=Symbol.for("react.fragment"),s=Symbol.for("react.strict_mode"),h=Symbol.for("react.profiler"),y=Symbol.for("react.consumer"),v=Symbol.for("react.context"),A=Symbol.for("react.forward_ref"),p=Symbol.for("react.suspense"),m=Symbol.for("react.memo"),R=Symbol.for("react.lazy"),M=Symbol.iterator;function S(b){return b===null||typeof b!="object"?null:(b=M&&b[M]||b["@@iterator"],typeof b=="function"?b:null)}var B={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},w=Object.assign,Y={};function Q(b,H,K){this.props=b,this.context=H,this.refs=Y,this.updater=K||B}Q.prototype.isReactComponent={},Q.prototype.setState=function(b,H){if(typeof b!="object"&&typeof b!="function"&&b!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,b,H,"setState")},Q.prototype.forceUpdate=function(b){this.updater.enqueueForceUpdate(this,b,"forceUpdate")};function q(){}q.prototype=Q.prototype;function Z(b,H,K){this.props=b,this.context=H,this.refs=Y,this.updater=K||B}var L=Z.prototype=new q;L.constructor=Z,w(L,Q.prototype),L.isPureReactComponent=!0;var $=Array.isArray,J={H:null,A:null,T:null,S:null,V:null},me=Object.prototype.hasOwnProperty;function V(b,H,K,G,F,oe){return K=oe.ref,{$$typeof:i,type:b,key:H,ref:K!==void 0?K:null,props:oe}}function fe(b,H){return V(b.type,H,void 0,void 0,void 0,b.props)}function ee(b){return typeof b=="object"&&b!==null&&b.$$typeof===i}function Ce(b){var H={"=":"=0",":":"=2"};return"$"+b.replace(/[=:]/g,function(K){return H[K]})}var yt=/\/+/g;function Je(b,H){return typeof b=="object"&&b!==null&&b.key!=null?Ce(""+b.key):H.toString(36)}function Ml(){}function zl(b){switch(b.status){case"fulfilled":return b.value;case"rejected":throw b.reason;default:switch(typeof b.status=="string"?b.then(Ml,Ml):(b.status="pending",b.then(function(H){b.status==="pending"&&(b.status="fulfilled",b.value=H)},function(H){b.status==="pending"&&(b.status="rejected",b.reason=H)})),b.status){case"fulfilled":return b.value;case"rejected":throw b.reason}}throw b}function ke(b,H,K,G,F){var oe=typeof b;(oe==="undefined"||oe==="boolean")&&(b=null);var ae=!1;if(b===null)ae=!0;else switch(oe){case"bigint":case"string":case"number":ae=!0;break;case"object":switch(b.$$typeof){case i:case d:ae=!0;break;case R:return ae=b._init,ke(ae(b._payload),H,K,G,F)}}if(ae)return F=F(b),ae=G===""?"."+Je(b,0):G,$(F)?(K="",ae!=null&&(K=ae.replace(yt,"$&/")+"/"),ke(F,H,K,"",function(nl){return nl})):F!=null&&(ee(F)&&(F=fe(F,K+(F.key==null||b&&b.key===F.key?"":(""+F.key).replace(yt,"$&/")+"/")+ae)),H.push(F)),1;ae=0;var ut=G===""?".":G+":";if($(b))for(var Te=0;Te>>1,b=C[xe];if(0>>1;xeh(G,te))Fh(oe,G)?(C[xe]=oe,C[F]=te,xe=F):(C[xe]=G,C[K]=te,xe=K);else if(Fh(oe,te))C[xe]=oe,C[F]=te,xe=F;else break e}}return X}function h(C,X){var te=C.sortIndex-X.sortIndex;return te!==0?te:C.id-X.id}if(i.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var y=performance;i.unstable_now=function(){return y.now()}}else{var v=Date,A=v.now();i.unstable_now=function(){return v.now()-A}}var p=[],m=[],R=1,M=null,S=3,B=!1,w=!1,Y=!1,Q=!1,q=typeof setTimeout=="function"?setTimeout:null,Z=typeof clearTimeout=="function"?clearTimeout:null,L=typeof setImmediate<"u"?setImmediate:null;function $(C){for(var X=o(m);X!==null;){if(X.callback===null)s(m);else if(X.startTime<=C)s(m),X.sortIndex=X.expirationTime,d(p,X);else break;X=o(m)}}function J(C){if(Y=!1,$(C),!w)if(o(p)!==null)w=!0,me||(me=!0,Je());else{var X=o(m);X!==null&&ke(J,X.startTime-C)}}var me=!1,V=-1,fe=5,ee=-1;function Ce(){return Q?!0:!(i.unstable_now()-eeC&&Ce());){var xe=M.callback;if(typeof xe=="function"){M.callback=null,S=M.priorityLevel;var b=xe(M.expirationTime<=C);if(C=i.unstable_now(),typeof b=="function"){M.callback=b,$(C),X=!0;break t}M===o(p)&&s(p),$(C)}else s(p);M=o(p)}if(M!==null)X=!0;else{var H=o(m);H!==null&&ke(J,H.startTime-C),X=!1}}break e}finally{M=null,S=te,B=!1}X=void 0}}finally{X?Je():me=!1}}}var Je;if(typeof L=="function")Je=function(){L(yt)};else if(typeof MessageChannel<"u"){var Ml=new MessageChannel,zl=Ml.port2;Ml.port1.onmessage=yt,Je=function(){zl.postMessage(null)}}else Je=function(){q(yt,0)};function ke(C,X){V=q(function(){C(i.unstable_now())},X)}i.unstable_IdlePriority=5,i.unstable_ImmediatePriority=1,i.unstable_LowPriority=4,i.unstable_NormalPriority=3,i.unstable_Profiling=null,i.unstable_UserBlockingPriority=2,i.unstable_cancelCallback=function(C){C.callback=null},i.unstable_forceFrameRate=function(C){0>C||125xe?(C.sortIndex=te,d(m,C),o(p)===null&&C===o(m)&&(Y?(Z(V),V=-1):Y=!0,ke(J,te-xe))):(C.sortIndex=b,d(p,C),w||B||(w=!0,me||(me=!0,Je()))),C},i.unstable_shouldYield=Ce,i.unstable_wrapCallback=function(C){var X=S;return function(){var te=S;S=X;try{return C.apply(this,arguments)}finally{S=te}}}}(_r)),_r}var Kd;function F0(){return Kd||(Kd=1,Cr.exports=W0()),Cr.exports}var Ur={exports:{}},Fe={};/** + * @license React + * react-dom.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var Jd;function P0(){if(Jd)return Fe;Jd=1;var i=qr();function d(p){var m="https://react.dev/errors/"+p;if(1"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(i)}catch(d){console.error(d)}}return i(),Ur.exports=P0(),Ur.exports}/** + * @license React + * react-dom-client.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var $d;function ey(){if($d)return Ln;$d=1;var i=F0(),d=qr(),o=I0();function s(e){var t="https://react.dev/errors/"+e;if(1b||(e.current=xe[b],xe[b]=null,b--)}function G(e,t){b++,xe[b]=e.current,e.current=t}var F=H(null),oe=H(null),ae=H(null),ut=H(null);function Te(e,t){switch(G(ae,t),G(oe,e),G(F,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?yd(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)t=yd(t),e=gd(t,e);else switch(e){case"svg":e=1;break;case"math":e=2;break;default:e=0}}K(F),G(F,e)}function nl(){K(F),K(oe),K(ae)}function mi(e){e.memoizedState!==null&&G(ut,e);var t=F.current,l=gd(t,e.type);t!==l&&(G(oe,e),G(F,l))}function Zn(e){oe.current===e&&(K(F),K(oe)),ut.current===e&&(K(ut),zn._currentValue=te)}var yi=Object.prototype.hasOwnProperty,gi=i.unstable_scheduleCallback,vi=i.unstable_cancelCallback,jh=i.unstable_shouldYield,Nh=i.unstable_requestPaint,zt=i.unstable_now,Ah=i.unstable_getCurrentPriorityLevel,Jr=i.unstable_ImmediatePriority,kr=i.unstable_UserBlockingPriority,Vn=i.unstable_NormalPriority,Rh=i.unstable_LowPriority,$r=i.unstable_IdlePriority,Oh=i.log,Dh=i.unstable_setDisableYieldValue,Ba=null,it=null;function ul(e){if(typeof Oh=="function"&&Dh(e),it&&typeof it.setStrictMode=="function")try{it.setStrictMode(Ba,e)}catch{}}var ct=Math.clz32?Math.clz32:Ch,Mh=Math.log,zh=Math.LN2;function Ch(e){return e>>>=0,e===0?32:31-(Mh(e)/zh|0)|0}var Kn=256,Jn=4194304;function Cl(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e&4194048;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function kn(e,t,l){var a=e.pendingLanes;if(a===0)return 0;var n=0,u=e.suspendedLanes,c=e.pingedLanes;e=e.warmLanes;var f=a&134217727;return f!==0?(a=f&~u,a!==0?n=Cl(a):(c&=f,c!==0?n=Cl(c):l||(l=f&~e,l!==0&&(n=Cl(l))))):(f=a&~u,f!==0?n=Cl(f):c!==0?n=Cl(c):l||(l=a&~e,l!==0&&(n=Cl(l)))),n===0?0:t!==0&&t!==n&&(t&u)===0&&(u=n&-n,l=t&-t,u>=l||u===32&&(l&4194048)!==0)?t:n}function qa(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function _h(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function Wr(){var e=Kn;return Kn<<=1,(Kn&4194048)===0&&(Kn=256),e}function Fr(){var e=Jn;return Jn<<=1,(Jn&62914560)===0&&(Jn=4194304),e}function pi(e){for(var t=[],l=0;31>l;l++)t.push(e);return t}function Ya(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function Uh(e,t,l,a,n,u){var c=e.pendingLanes;e.pendingLanes=l,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=l,e.entangledLanes&=l,e.errorRecoveryDisabledLanes&=l,e.shellSuspendCounter=0;var f=e.entanglements,g=e.expirationTimes,N=e.hiddenUpdates;for(l=c&~l;0)":-1n||g[a]!==N[n]){var z=` +`+g[a].replace(" at new "," at ");return e.displayName&&z.includes("")&&(z=z.replace("",e.displayName)),z}while(1<=a&&0<=n);break}}}finally{ji=!1,Error.prepareStackTrace=l}return(l=e?e.displayName||e.name:"")?ta(l):""}function Yh(e){switch(e.tag){case 26:case 27:case 5:return ta(e.type);case 16:return ta("Lazy");case 13:return ta("Suspense");case 19:return ta("SuspenseList");case 0:case 15:return Ni(e.type,!1);case 11:return Ni(e.type.render,!1);case 1:return Ni(e.type,!0);case 31:return ta("Activity");default:return""}}function cs(e){try{var t="";do t+=Yh(e),e=e.return;while(e);return t}catch(l){return` +Error generating stack: `+l.message+` +`+l.stack}}function gt(e){switch(typeof e){case"bigint":case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function rs(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function Gh(e){var t=rs(e)?"checked":"value",l=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),a=""+e[t];if(!e.hasOwnProperty(t)&&typeof l<"u"&&typeof l.get=="function"&&typeof l.set=="function"){var n=l.get,u=l.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return n.call(this)},set:function(c){a=""+c,u.call(this,c)}}),Object.defineProperty(e,t,{enumerable:l.enumerable}),{getValue:function(){return a},setValue:function(c){a=""+c},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function Fn(e){e._valueTracker||(e._valueTracker=Gh(e))}function ss(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var l=t.getValue(),a="";return e&&(a=rs(e)?e.checked?"true":"false":e.value),e=a,e!==l?(t.setValue(e),!0):!1}function Pn(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}var Xh=/[\n"\\]/g;function vt(e){return e.replace(Xh,function(t){return"\\"+t.charCodeAt(0).toString(16)+" "})}function Ai(e,t,l,a,n,u,c,f){e.name="",c!=null&&typeof c!="function"&&typeof c!="symbol"&&typeof c!="boolean"?e.type=c:e.removeAttribute("type"),t!=null?c==="number"?(t===0&&e.value===""||e.value!=t)&&(e.value=""+gt(t)):e.value!==""+gt(t)&&(e.value=""+gt(t)):c!=="submit"&&c!=="reset"||e.removeAttribute("value"),t!=null?Ri(e,c,gt(t)):l!=null?Ri(e,c,gt(l)):a!=null&&e.removeAttribute("value"),n==null&&u!=null&&(e.defaultChecked=!!u),n!=null&&(e.checked=n&&typeof n!="function"&&typeof n!="symbol"),f!=null&&typeof f!="function"&&typeof f!="symbol"&&typeof f!="boolean"?e.name=""+gt(f):e.removeAttribute("name")}function fs(e,t,l,a,n,u,c,f){if(u!=null&&typeof u!="function"&&typeof u!="symbol"&&typeof u!="boolean"&&(e.type=u),t!=null||l!=null){if(!(u!=="submit"&&u!=="reset"||t!=null))return;l=l!=null?""+gt(l):"",t=t!=null?""+gt(t):l,f||t===e.value||(e.value=t),e.defaultValue=t}a=a??n,a=typeof a!="function"&&typeof a!="symbol"&&!!a,e.checked=f?e.checked:!!a,e.defaultChecked=!!a,c!=null&&typeof c!="function"&&typeof c!="symbol"&&typeof c!="boolean"&&(e.name=c)}function Ri(e,t,l){t==="number"&&Pn(e.ownerDocument)===e||e.defaultValue===""+l||(e.defaultValue=""+l)}function la(e,t,l,a){if(e=e.options,t){t={};for(var n=0;n"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),Ci=!1;if(Gt)try{var Za={};Object.defineProperty(Za,"passive",{get:function(){Ci=!0}}),window.addEventListener("test",Za,Za),window.removeEventListener("test",Za,Za)}catch{Ci=!1}var cl=null,_i=null,eu=null;function vs(){if(eu)return eu;var e,t=_i,l=t.length,a,n="value"in cl?cl.value:cl.textContent,u=n.length;for(e=0;e=Ja),Ts=" ",js=!1;function Ns(e,t){switch(e){case"keyup":return gm.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function As(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var ia=!1;function pm(e,t){switch(e){case"compositionend":return As(t);case"keypress":return t.which!==32?null:(js=!0,Ts);case"textInput":return e=t.data,e===Ts&&js?null:e;default:return null}}function bm(e,t){if(ia)return e==="compositionend"||!Bi&&Ns(e,t)?(e=vs(),eu=_i=cl=null,ia=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:l,offset:t-e};e=a}e:{for(;l;){if(l.nextSibling){l=l.nextSibling;break e}l=l.parentNode}l=void 0}l=Us(l)}}function Hs(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Hs(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function Ls(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var t=Pn(e.document);t instanceof e.HTMLIFrameElement;){try{var l=typeof t.contentWindow.location.href=="string"}catch{l=!1}if(l)e=t.contentWindow;else break;t=Pn(e.document)}return t}function Gi(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}var Rm=Gt&&"documentMode"in document&&11>=document.documentMode,ca=null,Xi=null,Fa=null,Qi=!1;function Bs(e,t,l){var a=l.window===l?l.document:l.nodeType===9?l:l.ownerDocument;Qi||ca==null||ca!==Pn(a)||(a=ca,"selectionStart"in a&&Gi(a)?a={start:a.selectionStart,end:a.selectionEnd}:(a=(a.ownerDocument&&a.ownerDocument.defaultView||window).getSelection(),a={anchorNode:a.anchorNode,anchorOffset:a.anchorOffset,focusNode:a.focusNode,focusOffset:a.focusOffset}),Fa&&Wa(Fa,a)||(Fa=a,a=Zu(Xi,"onSelect"),0>=c,n-=c,Qt=1<<32-ct(t)+n|l<u?u:8;var c=C.T,f={};C.T=f,Oc(e,!1,t,l);try{var g=n(),N=C.S;if(N!==null&&N(f,g),g!==null&&typeof g=="object"&&typeof g.then=="function"){var z=Hm(g,a);hn(e,t,z,ht(e))}else hn(e,t,a,ht(e))}catch(U){hn(e,t,{then:function(){},status:"rejected",reason:U},ht())}finally{X.p=u,C.T=c}}function Gm(){}function Ac(e,t,l,a){if(e.tag!==5)throw Error(s(476));var n=Yf(e).queue;qf(e,n,t,te,l===null?Gm:function(){return Gf(e),l(a)})}function Yf(e){var t=e.memoizedState;if(t!==null)return t;t={memoizedState:te,baseState:te,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Jt,lastRenderedState:te},next:null};var l={};return t.next={memoizedState:l,baseState:l,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Jt,lastRenderedState:l},next:null},e.memoizedState=t,e=e.alternate,e!==null&&(e.memoizedState=t),t}function Gf(e){var t=Yf(e).next.queue;hn(e,t,{},ht())}function Rc(){return We(zn)}function Xf(){return Ue().memoizedState}function Qf(){return Ue().memoizedState}function Xm(e){for(var t=e.return;t!==null;){switch(t.tag){case 24:case 3:var l=ht();e=fl(l);var a=ol(t,e,l);a!==null&&(mt(a,t,l),cn(a,t,l)),t={cache:ac()},e.payload=t;return}t=t.return}}function Qm(e,t,l){var a=ht();l={lane:a,revertLane:0,action:l,hasEagerState:!1,eagerState:null,next:null},ju(e)?Vf(t,l):(l=Ji(e,t,l,a),l!==null&&(mt(l,e,a),Kf(l,t,a)))}function Zf(e,t,l){var a=ht();hn(e,t,l,a)}function hn(e,t,l,a){var n={lane:a,revertLane:0,action:l,hasEagerState:!1,eagerState:null,next:null};if(ju(e))Vf(t,n);else{var u=e.alternate;if(e.lanes===0&&(u===null||u.lanes===0)&&(u=t.lastRenderedReducer,u!==null))try{var c=t.lastRenderedState,f=u(c,l);if(n.hasEagerState=!0,n.eagerState=f,rt(f,c))return cu(e,t,n,0),Ee===null&&iu(),!1}catch{}finally{}if(l=Ji(e,t,n,a),l!==null)return mt(l,e,a),Kf(l,t,a),!0}return!1}function Oc(e,t,l,a){if(a={lane:2,revertLane:ir(),action:a,hasEagerState:!1,eagerState:null,next:null},ju(e)){if(t)throw Error(s(479))}else t=Ji(e,l,a,2),t!==null&&mt(t,e,2)}function ju(e){var t=e.alternate;return e===ue||t!==null&&t===ue}function Vf(e,t){va=pu=!0;var l=e.pending;l===null?t.next=t:(t.next=l.next,l.next=t),e.pending=t}function Kf(e,t,l){if((l&4194048)!==0){var a=t.lanes;a&=e.pendingLanes,l|=a,t.lanes=l,Ir(e,l)}}var Nu={readContext:We,use:xu,useCallback:Me,useContext:Me,useEffect:Me,useImperativeHandle:Me,useLayoutEffect:Me,useInsertionEffect:Me,useMemo:Me,useReducer:Me,useRef:Me,useState:Me,useDebugValue:Me,useDeferredValue:Me,useTransition:Me,useSyncExternalStore:Me,useId:Me,useHostTransitionStatus:Me,useFormState:Me,useActionState:Me,useOptimistic:Me,useMemoCache:Me,useCacheRefresh:Me},Jf={readContext:We,use:xu,useCallback:function(e,t){return tt().memoizedState=[e,t===void 0?null:t],e},useContext:We,useEffect:Mf,useImperativeHandle:function(e,t,l){l=l!=null?l.concat([e]):null,Tu(4194308,4,Uf.bind(null,t,e),l)},useLayoutEffect:function(e,t){return Tu(4194308,4,e,t)},useInsertionEffect:function(e,t){Tu(4,2,e,t)},useMemo:function(e,t){var l=tt();t=t===void 0?null:t;var a=e();if(Vl){ul(!0);try{e()}finally{ul(!1)}}return l.memoizedState=[a,t],a},useReducer:function(e,t,l){var a=tt();if(l!==void 0){var n=l(t);if(Vl){ul(!0);try{l(t)}finally{ul(!1)}}}else n=t;return a.memoizedState=a.baseState=n,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:n},a.queue=e,e=e.dispatch=Qm.bind(null,ue,e),[a.memoizedState,e]},useRef:function(e){var t=tt();return e={current:e},t.memoizedState=e},useState:function(e){e=Ec(e);var t=e.queue,l=Zf.bind(null,ue,t);return t.dispatch=l,[e.memoizedState,l]},useDebugValue:jc,useDeferredValue:function(e,t){var l=tt();return Nc(l,e,t)},useTransition:function(){var e=Ec(!1);return e=qf.bind(null,ue,e.queue,!0,!1),tt().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,l){var a=ue,n=tt();if(he){if(l===void 0)throw Error(s(407));l=l()}else{if(l=t(),Ee===null)throw Error(s(349));(se&124)!==0||mf(a,t,l)}n.memoizedState=l;var u={value:l,getSnapshot:t};return n.queue=u,Mf(gf.bind(null,a,u,e),[e]),a.flags|=2048,ba(9,Eu(),yf.bind(null,a,u,l,t),null),l},useId:function(){var e=tt(),t=Ee.identifierPrefix;if(he){var l=Zt,a=Qt;l=(a&~(1<<32-ct(a)-1)).toString(32)+l,t="«"+t+"R"+l,l=bu++,0I?(Ge=W,W=null):Ge=W.sibling;var de=O(T,W,j[I],_);if(de===null){W===null&&(W=Ge);break}e&&W&&de.alternate===null&&t(T,W),x=u(de,x,I),ie===null?k=de:ie.sibling=de,ie=de,W=Ge}if(I===j.length)return l(T,W),he&&ql(T,I),k;if(W===null){for(;II?(Ge=W,W=null):Ge=W.sibling;var Ol=O(T,W,de.value,_);if(Ol===null){W===null&&(W=Ge);break}e&&W&&Ol.alternate===null&&t(T,W),x=u(Ol,x,I),ie===null?k=Ol:ie.sibling=Ol,ie=Ol,W=Ge}if(de.done)return l(T,W),he&&ql(T,I),k;if(W===null){for(;!de.done;I++,de=j.next())de=U(T,de.value,_),de!==null&&(x=u(de,x,I),ie===null?k=de:ie.sibling=de,ie=de);return he&&ql(T,I),k}for(W=a(W);!de.done;I++,de=j.next())de=D(W,T,I,de.value,_),de!==null&&(e&&de.alternate!==null&&W.delete(de.key===null?I:de.key),x=u(de,x,I),ie===null?k=de:ie.sibling=de,ie=de);return e&&W.forEach(function(V0){return t(T,V0)}),he&&ql(T,I),k}function be(T,x,j,_){if(typeof j=="object"&&j!==null&&j.type===w&&j.key===null&&(j=j.props.children),typeof j=="object"&&j!==null){switch(j.$$typeof){case S:e:{for(var k=j.key;x!==null;){if(x.key===k){if(k=j.type,k===w){if(x.tag===7){l(T,x.sibling),_=n(x,j.props.children),_.return=T,T=_;break e}}else if(x.elementType===k||typeof k=="object"&&k!==null&&k.$$typeof===fe&&$f(k)===x.type){l(T,x.sibling),_=n(x,j.props),yn(_,j),_.return=T,T=_;break e}l(T,x);break}else t(T,x);x=x.sibling}j.type===w?(_=Ll(j.props.children,T.mode,_,j.key),_.return=T,T=_):(_=su(j.type,j.key,j.props,null,T.mode,_),yn(_,j),_.return=T,T=_)}return c(T);case B:e:{for(k=j.key;x!==null;){if(x.key===k)if(x.tag===4&&x.stateNode.containerInfo===j.containerInfo&&x.stateNode.implementation===j.implementation){l(T,x.sibling),_=n(x,j.children||[]),_.return=T,T=_;break e}else{l(T,x);break}else t(T,x);x=x.sibling}_=Wi(j,T.mode,_),_.return=T,T=_}return c(T);case fe:return k=j._init,j=k(j._payload),be(T,x,j,_)}if(ke(j))return le(T,x,j,_);if(Je(j)){if(k=Je(j),typeof k!="function")throw Error(s(150));return j=k.call(j),P(T,x,j,_)}if(typeof j.then=="function")return be(T,x,Au(j),_);if(j.$$typeof===L)return be(T,x,hu(T,j),_);Ru(T,j)}return typeof j=="string"&&j!==""||typeof j=="number"||typeof j=="bigint"?(j=""+j,x!==null&&x.tag===6?(l(T,x.sibling),_=n(x,j),_.return=T,T=_):(l(T,x),_=$i(j,T.mode,_),_.return=T,T=_),c(T)):l(T,x)}return function(T,x,j,_){try{mn=0;var k=be(T,x,j,_);return xa=null,k}catch(W){if(W===nn||W===yu)throw W;var ie=st(29,W,null,T.mode);return ie.lanes=_,ie.return=T,ie}finally{}}}var Sa=Wf(!0),Ff=Wf(!1),Et=H(null),_t=null;function hl(e){var t=e.alternate;G(He,He.current&1),G(Et,e),_t===null&&(t===null||ga.current!==null||t.memoizedState!==null)&&(_t=e)}function Pf(e){if(e.tag===22){if(G(He,He.current),G(Et,e),_t===null){var t=e.alternate;t!==null&&t.memoizedState!==null&&(_t=e)}}else ml()}function ml(){G(He,He.current),G(Et,Et.current)}function kt(e){K(Et),_t===e&&(_t=null),K(He)}var He=H(0);function Ou(e){for(var t=e;t!==null;){if(t.tag===13){var l=t.memoizedState;if(l!==null&&(l=l.dehydrated,l===null||l.data==="$?"||pr(l)))return t}else if(t.tag===19&&t.memoizedProps.revealOrder!==void 0){if((t.flags&128)!==0)return t}else if(t.child!==null){t.child.return=t,t=t.child;continue}if(t===e)break;for(;t.sibling===null;){if(t.return===null||t.return===e)return null;t=t.return}t.sibling.return=t.return,t=t.sibling}return null}function Dc(e,t,l,a){t=e.memoizedState,l=l(a,t),l=l==null?t:R({},t,l),e.memoizedState=l,e.lanes===0&&(e.updateQueue.baseState=l)}var Mc={enqueueSetState:function(e,t,l){e=e._reactInternals;var a=ht(),n=fl(a);n.payload=t,l!=null&&(n.callback=l),t=ol(e,n,a),t!==null&&(mt(t,e,a),cn(t,e,a))},enqueueReplaceState:function(e,t,l){e=e._reactInternals;var a=ht(),n=fl(a);n.tag=1,n.payload=t,l!=null&&(n.callback=l),t=ol(e,n,a),t!==null&&(mt(t,e,a),cn(t,e,a))},enqueueForceUpdate:function(e,t){e=e._reactInternals;var l=ht(),a=fl(l);a.tag=2,t!=null&&(a.callback=t),t=ol(e,a,l),t!==null&&(mt(t,e,l),cn(t,e,l))}};function If(e,t,l,a,n,u,c){return e=e.stateNode,typeof e.shouldComponentUpdate=="function"?e.shouldComponentUpdate(a,u,c):t.prototype&&t.prototype.isPureReactComponent?!Wa(l,a)||!Wa(n,u):!0}function eo(e,t,l,a){e=t.state,typeof t.componentWillReceiveProps=="function"&&t.componentWillReceiveProps(l,a),typeof t.UNSAFE_componentWillReceiveProps=="function"&&t.UNSAFE_componentWillReceiveProps(l,a),t.state!==e&&Mc.enqueueReplaceState(t,t.state,null)}function Kl(e,t){var l=t;if("ref"in t){l={};for(var a in t)a!=="ref"&&(l[a]=t[a])}if(e=e.defaultProps){l===t&&(l=R({},l));for(var n in e)l[n]===void 0&&(l[n]=e[n])}return l}var Du=typeof reportError=="function"?reportError:function(e){if(typeof window=="object"&&typeof window.ErrorEvent=="function"){var t=new window.ErrorEvent("error",{bubbles:!0,cancelable:!0,message:typeof e=="object"&&e!==null&&typeof e.message=="string"?String(e.message):String(e),error:e});if(!window.dispatchEvent(t))return}else if(typeof process=="object"&&typeof process.emit=="function"){process.emit("uncaughtException",e);return}console.error(e)};function to(e){Du(e)}function lo(e){console.error(e)}function ao(e){Du(e)}function Mu(e,t){try{var l=e.onUncaughtError;l(t.value,{componentStack:t.stack})}catch(a){setTimeout(function(){throw a})}}function no(e,t,l){try{var a=e.onCaughtError;a(l.value,{componentStack:l.stack,errorBoundary:t.tag===1?t.stateNode:null})}catch(n){setTimeout(function(){throw n})}}function zc(e,t,l){return l=fl(l),l.tag=3,l.payload={element:null},l.callback=function(){Mu(e,t)},l}function uo(e){return e=fl(e),e.tag=3,e}function io(e,t,l,a){var n=l.type.getDerivedStateFromError;if(typeof n=="function"){var u=a.value;e.payload=function(){return n(u)},e.callback=function(){no(t,l,a)}}var c=l.stateNode;c!==null&&typeof c.componentDidCatch=="function"&&(e.callback=function(){no(t,l,a),typeof n!="function"&&(xl===null?xl=new Set([this]):xl.add(this));var f=a.stack;this.componentDidCatch(a.value,{componentStack:f!==null?f:""})})}function Vm(e,t,l,a,n){if(l.flags|=32768,a!==null&&typeof a=="object"&&typeof a.then=="function"){if(t=l.alternate,t!==null&&tn(t,l,n,!0),l=Et.current,l!==null){switch(l.tag){case 13:return _t===null?tr():l.alternate===null&&Oe===0&&(Oe=3),l.flags&=-257,l.flags|=65536,l.lanes=n,a===ic?l.flags|=16384:(t=l.updateQueue,t===null?l.updateQueue=new Set([a]):t.add(a),ar(e,a,n)),!1;case 22:return l.flags|=65536,a===ic?l.flags|=16384:(t=l.updateQueue,t===null?(t={transitions:null,markerInstances:null,retryQueue:new Set([a])},l.updateQueue=t):(l=t.retryQueue,l===null?t.retryQueue=new Set([a]):l.add(a)),ar(e,a,n)),!1}throw Error(s(435,l.tag))}return ar(e,a,n),tr(),!1}if(he)return t=Et.current,t!==null?((t.flags&65536)===0&&(t.flags|=256),t.flags|=65536,t.lanes=n,a!==Ii&&(e=Error(s(422),{cause:a}),en(pt(e,l)))):(a!==Ii&&(t=Error(s(423),{cause:a}),en(pt(t,l))),e=e.current.alternate,e.flags|=65536,n&=-n,e.lanes|=n,a=pt(a,l),n=zc(e.stateNode,a,n),sc(e,n),Oe!==4&&(Oe=2)),!1;var u=Error(s(520),{cause:a});if(u=pt(u,l),En===null?En=[u]:En.push(u),Oe!==4&&(Oe=2),t===null)return!0;a=pt(a,l),l=t;do{switch(l.tag){case 3:return l.flags|=65536,e=n&-n,l.lanes|=e,e=zc(l.stateNode,a,e),sc(l,e),!1;case 1:if(t=l.type,u=l.stateNode,(l.flags&128)===0&&(typeof t.getDerivedStateFromError=="function"||u!==null&&typeof u.componentDidCatch=="function"&&(xl===null||!xl.has(u))))return l.flags|=65536,n&=-n,l.lanes|=n,n=uo(n),io(n,e,l,a),sc(l,n),!1}l=l.return}while(l!==null);return!1}var co=Error(s(461)),qe=!1;function Qe(e,t,l,a){t.child=e===null?Ff(t,null,l,a):Sa(t,e.child,l,a)}function ro(e,t,l,a,n){l=l.render;var u=t.ref;if("ref"in a){var c={};for(var f in a)f!=="ref"&&(c[f]=a[f])}else c=a;return Ql(t),a=mc(e,t,l,c,u,n),f=yc(),e!==null&&!qe?(gc(e,t,n),$t(e,t,n)):(he&&f&&Fi(t),t.flags|=1,Qe(e,t,a,n),t.child)}function so(e,t,l,a,n){if(e===null){var u=l.type;return typeof u=="function"&&!ki(u)&&u.defaultProps===void 0&&l.compare===null?(t.tag=15,t.type=u,fo(e,t,u,a,n)):(e=su(l.type,null,a,t,t.mode,n),e.ref=t.ref,e.return=t,t.child=e)}if(u=e.child,!qc(e,n)){var c=u.memoizedProps;if(l=l.compare,l=l!==null?l:Wa,l(c,a)&&e.ref===t.ref)return $t(e,t,n)}return t.flags|=1,e=Xt(u,a),e.ref=t.ref,e.return=t,t.child=e}function fo(e,t,l,a,n){if(e!==null){var u=e.memoizedProps;if(Wa(u,a)&&e.ref===t.ref)if(qe=!1,t.pendingProps=a=u,qc(e,n))(e.flags&131072)!==0&&(qe=!0);else return t.lanes=e.lanes,$t(e,t,n)}return Cc(e,t,l,a,n)}function oo(e,t,l){var a=t.pendingProps,n=a.children,u=e!==null?e.memoizedState:null;if(a.mode==="hidden"){if((t.flags&128)!==0){if(a=u!==null?u.baseLanes|l:l,e!==null){for(n=t.child=e.child,u=0;n!==null;)u=u|n.lanes|n.childLanes,n=n.sibling;t.childLanes=u&~a}else t.childLanes=0,t.child=null;return ho(e,t,a,l)}if((l&536870912)!==0)t.memoizedState={baseLanes:0,cachePool:null},e!==null&&mu(t,u!==null?u.cachePool:null),u!==null?ff(t,u):oc(),Pf(t);else return t.lanes=t.childLanes=536870912,ho(e,t,u!==null?u.baseLanes|l:l,l)}else u!==null?(mu(t,u.cachePool),ff(t,u),ml(),t.memoizedState=null):(e!==null&&mu(t,null),oc(),ml());return Qe(e,t,n,l),t.child}function ho(e,t,l,a){var n=uc();return n=n===null?null:{parent:we._currentValue,pool:n},t.memoizedState={baseLanes:l,cachePool:n},e!==null&&mu(t,null),oc(),Pf(t),e!==null&&tn(e,t,a,!0),null}function zu(e,t){var l=t.ref;if(l===null)e!==null&&e.ref!==null&&(t.flags|=4194816);else{if(typeof l!="function"&&typeof l!="object")throw Error(s(284));(e===null||e.ref!==l)&&(t.flags|=4194816)}}function Cc(e,t,l,a,n){return Ql(t),l=mc(e,t,l,a,void 0,n),a=yc(),e!==null&&!qe?(gc(e,t,n),$t(e,t,n)):(he&&a&&Fi(t),t.flags|=1,Qe(e,t,l,n),t.child)}function mo(e,t,l,a,n,u){return Ql(t),t.updateQueue=null,l=df(t,a,l,n),of(e),a=yc(),e!==null&&!qe?(gc(e,t,u),$t(e,t,u)):(he&&a&&Fi(t),t.flags|=1,Qe(e,t,l,u),t.child)}function yo(e,t,l,a,n){if(Ql(t),t.stateNode===null){var u=oa,c=l.contextType;typeof c=="object"&&c!==null&&(u=We(c)),u=new l(a,u),t.memoizedState=u.state!==null&&u.state!==void 0?u.state:null,u.updater=Mc,t.stateNode=u,u._reactInternals=t,u=t.stateNode,u.props=a,u.state=t.memoizedState,u.refs={},cc(t),c=l.contextType,u.context=typeof c=="object"&&c!==null?We(c):oa,u.state=t.memoizedState,c=l.getDerivedStateFromProps,typeof c=="function"&&(Dc(t,l,c,a),u.state=t.memoizedState),typeof l.getDerivedStateFromProps=="function"||typeof u.getSnapshotBeforeUpdate=="function"||typeof u.UNSAFE_componentWillMount!="function"&&typeof u.componentWillMount!="function"||(c=u.state,typeof u.componentWillMount=="function"&&u.componentWillMount(),typeof u.UNSAFE_componentWillMount=="function"&&u.UNSAFE_componentWillMount(),c!==u.state&&Mc.enqueueReplaceState(u,u.state,null),sn(t,a,u,n),rn(),u.state=t.memoizedState),typeof u.componentDidMount=="function"&&(t.flags|=4194308),a=!0}else if(e===null){u=t.stateNode;var f=t.memoizedProps,g=Kl(l,f);u.props=g;var N=u.context,z=l.contextType;c=oa,typeof z=="object"&&z!==null&&(c=We(z));var U=l.getDerivedStateFromProps;z=typeof U=="function"||typeof u.getSnapshotBeforeUpdate=="function",f=t.pendingProps!==f,z||typeof u.UNSAFE_componentWillReceiveProps!="function"&&typeof u.componentWillReceiveProps!="function"||(f||N!==c)&&eo(t,u,a,c),sl=!1;var O=t.memoizedState;u.state=O,sn(t,a,u,n),rn(),N=t.memoizedState,f||O!==N||sl?(typeof U=="function"&&(Dc(t,l,U,a),N=t.memoizedState),(g=sl||If(t,l,g,a,O,N,c))?(z||typeof u.UNSAFE_componentWillMount!="function"&&typeof u.componentWillMount!="function"||(typeof u.componentWillMount=="function"&&u.componentWillMount(),typeof u.UNSAFE_componentWillMount=="function"&&u.UNSAFE_componentWillMount()),typeof u.componentDidMount=="function"&&(t.flags|=4194308)):(typeof u.componentDidMount=="function"&&(t.flags|=4194308),t.memoizedProps=a,t.memoizedState=N),u.props=a,u.state=N,u.context=c,a=g):(typeof u.componentDidMount=="function"&&(t.flags|=4194308),a=!1)}else{u=t.stateNode,rc(e,t),c=t.memoizedProps,z=Kl(l,c),u.props=z,U=t.pendingProps,O=u.context,N=l.contextType,g=oa,typeof N=="object"&&N!==null&&(g=We(N)),f=l.getDerivedStateFromProps,(N=typeof f=="function"||typeof u.getSnapshotBeforeUpdate=="function")||typeof u.UNSAFE_componentWillReceiveProps!="function"&&typeof u.componentWillReceiveProps!="function"||(c!==U||O!==g)&&eo(t,u,a,g),sl=!1,O=t.memoizedState,u.state=O,sn(t,a,u,n),rn();var D=t.memoizedState;c!==U||O!==D||sl||e!==null&&e.dependencies!==null&&du(e.dependencies)?(typeof f=="function"&&(Dc(t,l,f,a),D=t.memoizedState),(z=sl||If(t,l,z,a,O,D,g)||e!==null&&e.dependencies!==null&&du(e.dependencies))?(N||typeof u.UNSAFE_componentWillUpdate!="function"&&typeof u.componentWillUpdate!="function"||(typeof u.componentWillUpdate=="function"&&u.componentWillUpdate(a,D,g),typeof u.UNSAFE_componentWillUpdate=="function"&&u.UNSAFE_componentWillUpdate(a,D,g)),typeof u.componentDidUpdate=="function"&&(t.flags|=4),typeof u.getSnapshotBeforeUpdate=="function"&&(t.flags|=1024)):(typeof u.componentDidUpdate!="function"||c===e.memoizedProps&&O===e.memoizedState||(t.flags|=4),typeof u.getSnapshotBeforeUpdate!="function"||c===e.memoizedProps&&O===e.memoizedState||(t.flags|=1024),t.memoizedProps=a,t.memoizedState=D),u.props=a,u.state=D,u.context=g,a=z):(typeof u.componentDidUpdate!="function"||c===e.memoizedProps&&O===e.memoizedState||(t.flags|=4),typeof u.getSnapshotBeforeUpdate!="function"||c===e.memoizedProps&&O===e.memoizedState||(t.flags|=1024),a=!1)}return u=a,zu(e,t),a=(t.flags&128)!==0,u||a?(u=t.stateNode,l=a&&typeof l.getDerivedStateFromError!="function"?null:u.render(),t.flags|=1,e!==null&&a?(t.child=Sa(t,e.child,null,n),t.child=Sa(t,null,l,n)):Qe(e,t,l,n),t.memoizedState=u.state,e=t.child):e=$t(e,t,n),e}function go(e,t,l,a){return Ia(),t.flags|=256,Qe(e,t,l,a),t.child}var _c={dehydrated:null,treeContext:null,retryLane:0,hydrationErrors:null};function Uc(e){return{baseLanes:e,cachePool:tf()}}function wc(e,t,l){return e=e!==null?e.childLanes&~l:0,t&&(e|=Tt),e}function vo(e,t,l){var a=t.pendingProps,n=!1,u=(t.flags&128)!==0,c;if((c=u)||(c=e!==null&&e.memoizedState===null?!1:(He.current&2)!==0),c&&(n=!0,t.flags&=-129),c=(t.flags&32)!==0,t.flags&=-33,e===null){if(he){if(n?hl(t):ml(),he){var f=Re,g;if(g=f){e:{for(g=f,f=Ct;g.nodeType!==8;){if(!f){f=null;break e}if(g=Dt(g.nextSibling),g===null){f=null;break e}}f=g}f!==null?(t.memoizedState={dehydrated:f,treeContext:Bl!==null?{id:Qt,overflow:Zt}:null,retryLane:536870912,hydrationErrors:null},g=st(18,null,null,0),g.stateNode=f,g.return=t,t.child=g,Pe=t,Re=null,g=!0):g=!1}g||Gl(t)}if(f=t.memoizedState,f!==null&&(f=f.dehydrated,f!==null))return pr(f)?t.lanes=32:t.lanes=536870912,null;kt(t)}return f=a.children,a=a.fallback,n?(ml(),n=t.mode,f=Cu({mode:"hidden",children:f},n),a=Ll(a,n,l,null),f.return=t,a.return=t,f.sibling=a,t.child=f,n=t.child,n.memoizedState=Uc(l),n.childLanes=wc(e,c,l),t.memoizedState=_c,a):(hl(t),Hc(t,f))}if(g=e.memoizedState,g!==null&&(f=g.dehydrated,f!==null)){if(u)t.flags&256?(hl(t),t.flags&=-257,t=Lc(e,t,l)):t.memoizedState!==null?(ml(),t.child=e.child,t.flags|=128,t=null):(ml(),n=a.fallback,f=t.mode,a=Cu({mode:"visible",children:a.children},f),n=Ll(n,f,l,null),n.flags|=2,a.return=t,n.return=t,a.sibling=n,t.child=a,Sa(t,e.child,null,l),a=t.child,a.memoizedState=Uc(l),a.childLanes=wc(e,c,l),t.memoizedState=_c,t=n);else if(hl(t),pr(f)){if(c=f.nextSibling&&f.nextSibling.dataset,c)var N=c.dgst;c=N,a=Error(s(419)),a.stack="",a.digest=c,en({value:a,source:null,stack:null}),t=Lc(e,t,l)}else if(qe||tn(e,t,l,!1),c=(l&e.childLanes)!==0,qe||c){if(c=Ee,c!==null&&(a=l&-l,a=(a&42)!==0?1:bi(a),a=(a&(c.suspendedLanes|l))!==0?0:a,a!==0&&a!==g.retryLane))throw g.retryLane=a,fa(e,a),mt(c,e,a),co;f.data==="$?"||tr(),t=Lc(e,t,l)}else f.data==="$?"?(t.flags|=192,t.child=e.child,t=null):(e=g.treeContext,Re=Dt(f.nextSibling),Pe=t,he=!0,Yl=null,Ct=!1,e!==null&&(xt[St++]=Qt,xt[St++]=Zt,xt[St++]=Bl,Qt=e.id,Zt=e.overflow,Bl=t),t=Hc(t,a.children),t.flags|=4096);return t}return n?(ml(),n=a.fallback,f=t.mode,g=e.child,N=g.sibling,a=Xt(g,{mode:"hidden",children:a.children}),a.subtreeFlags=g.subtreeFlags&65011712,N!==null?n=Xt(N,n):(n=Ll(n,f,l,null),n.flags|=2),n.return=t,a.return=t,a.sibling=n,t.child=a,a=n,n=t.child,f=e.child.memoizedState,f===null?f=Uc(l):(g=f.cachePool,g!==null?(N=we._currentValue,g=g.parent!==N?{parent:N,pool:N}:g):g=tf(),f={baseLanes:f.baseLanes|l,cachePool:g}),n.memoizedState=f,n.childLanes=wc(e,c,l),t.memoizedState=_c,a):(hl(t),l=e.child,e=l.sibling,l=Xt(l,{mode:"visible",children:a.children}),l.return=t,l.sibling=null,e!==null&&(c=t.deletions,c===null?(t.deletions=[e],t.flags|=16):c.push(e)),t.child=l,t.memoizedState=null,l)}function Hc(e,t){return t=Cu({mode:"visible",children:t},e.mode),t.return=e,e.child=t}function Cu(e,t){return e=st(22,e,null,t),e.lanes=0,e.stateNode={_visibility:1,_pendingMarkers:null,_retryCache:null,_transitions:null},e}function Lc(e,t,l){return Sa(t,e.child,null,l),e=Hc(t,t.pendingProps.children),e.flags|=2,t.memoizedState=null,e}function po(e,t,l){e.lanes|=t;var a=e.alternate;a!==null&&(a.lanes|=t),tc(e.return,t,l)}function Bc(e,t,l,a,n){var u=e.memoizedState;u===null?e.memoizedState={isBackwards:t,rendering:null,renderingStartTime:0,last:a,tail:l,tailMode:n}:(u.isBackwards=t,u.rendering=null,u.renderingStartTime=0,u.last=a,u.tail=l,u.tailMode=n)}function bo(e,t,l){var a=t.pendingProps,n=a.revealOrder,u=a.tail;if(Qe(e,t,a.children,l),a=He.current,(a&2)!==0)a=a&1|2,t.flags|=128;else{if(e!==null&&(e.flags&128)!==0)e:for(e=t.child;e!==null;){if(e.tag===13)e.memoizedState!==null&&po(e,l,t);else if(e.tag===19)po(e,l,t);else if(e.child!==null){e.child.return=e,e=e.child;continue}if(e===t)break e;for(;e.sibling===null;){if(e.return===null||e.return===t)break e;e=e.return}e.sibling.return=e.return,e=e.sibling}a&=1}switch(G(He,a),n){case"forwards":for(l=t.child,n=null;l!==null;)e=l.alternate,e!==null&&Ou(e)===null&&(n=l),l=l.sibling;l=n,l===null?(n=t.child,t.child=null):(n=l.sibling,l.sibling=null),Bc(t,!1,n,l,u);break;case"backwards":for(l=null,n=t.child,t.child=null;n!==null;){if(e=n.alternate,e!==null&&Ou(e)===null){t.child=n;break}e=n.sibling,n.sibling=l,l=n,n=e}Bc(t,!0,l,null,u);break;case"together":Bc(t,!1,null,null,void 0);break;default:t.memoizedState=null}return t.child}function $t(e,t,l){if(e!==null&&(t.dependencies=e.dependencies),bl|=t.lanes,(l&t.childLanes)===0)if(e!==null){if(tn(e,t,l,!1),(l&t.childLanes)===0)return null}else return null;if(e!==null&&t.child!==e.child)throw Error(s(153));if(t.child!==null){for(e=t.child,l=Xt(e,e.pendingProps),t.child=l,l.return=t;e.sibling!==null;)e=e.sibling,l=l.sibling=Xt(e,e.pendingProps),l.return=t;l.sibling=null}return t.child}function qc(e,t){return(e.lanes&t)!==0?!0:(e=e.dependencies,!!(e!==null&&du(e)))}function Km(e,t,l){switch(t.tag){case 3:Te(t,t.stateNode.containerInfo),rl(t,we,e.memoizedState.cache),Ia();break;case 27:case 5:mi(t);break;case 4:Te(t,t.stateNode.containerInfo);break;case 10:rl(t,t.type,t.memoizedProps.value);break;case 13:var a=t.memoizedState;if(a!==null)return a.dehydrated!==null?(hl(t),t.flags|=128,null):(l&t.child.childLanes)!==0?vo(e,t,l):(hl(t),e=$t(e,t,l),e!==null?e.sibling:null);hl(t);break;case 19:var n=(e.flags&128)!==0;if(a=(l&t.childLanes)!==0,a||(tn(e,t,l,!1),a=(l&t.childLanes)!==0),n){if(a)return bo(e,t,l);t.flags|=128}if(n=t.memoizedState,n!==null&&(n.rendering=null,n.tail=null,n.lastEffect=null),G(He,He.current),a)break;return null;case 22:case 23:return t.lanes=0,oo(e,t,l);case 24:rl(t,we,e.memoizedState.cache)}return $t(e,t,l)}function xo(e,t,l){if(e!==null)if(e.memoizedProps!==t.pendingProps)qe=!0;else{if(!qc(e,l)&&(t.flags&128)===0)return qe=!1,Km(e,t,l);qe=(e.flags&131072)!==0}else qe=!1,he&&(t.flags&1048576)!==0&&ks(t,ou,t.index);switch(t.lanes=0,t.tag){case 16:e:{e=t.pendingProps;var a=t.elementType,n=a._init;if(a=n(a._payload),t.type=a,typeof a=="function")ki(a)?(e=Kl(a,e),t.tag=1,t=yo(null,t,a,e,l)):(t.tag=0,t=Cc(null,t,a,e,l));else{if(a!=null){if(n=a.$$typeof,n===$){t.tag=11,t=ro(null,t,a,e,l);break e}else if(n===V){t.tag=14,t=so(null,t,a,e,l);break e}}throw t=zl(a)||a,Error(s(306,t,""))}}return t;case 0:return Cc(e,t,t.type,t.pendingProps,l);case 1:return a=t.type,n=Kl(a,t.pendingProps),yo(e,t,a,n,l);case 3:e:{if(Te(t,t.stateNode.containerInfo),e===null)throw Error(s(387));a=t.pendingProps;var u=t.memoizedState;n=u.element,rc(e,t),sn(t,a,null,l);var c=t.memoizedState;if(a=c.cache,rl(t,we,a),a!==u.cache&&lc(t,[we],l,!0),rn(),a=c.element,u.isDehydrated)if(u={element:a,isDehydrated:!1,cache:c.cache},t.updateQueue.baseState=u,t.memoizedState=u,t.flags&256){t=go(e,t,a,l);break e}else if(a!==n){n=pt(Error(s(424)),t),en(n),t=go(e,t,a,l);break e}else{switch(e=t.stateNode.containerInfo,e.nodeType){case 9:e=e.body;break;default:e=e.nodeName==="HTML"?e.ownerDocument.body:e}for(Re=Dt(e.firstChild),Pe=t,he=!0,Yl=null,Ct=!0,l=Ff(t,null,a,l),t.child=l;l;)l.flags=l.flags&-3|4096,l=l.sibling}else{if(Ia(),a===n){t=$t(e,t,l);break e}Qe(e,t,a,l)}t=t.child}return t;case 26:return zu(e,t),e===null?(l=jd(t.type,null,t.pendingProps,null))?t.memoizedState=l:he||(l=t.type,e=t.pendingProps,a=Ku(ae.current).createElement(l),a[$e]=t,a[Ie]=e,Ve(a,l,e),Be(a),t.stateNode=a):t.memoizedState=jd(t.type,e.memoizedProps,t.pendingProps,e.memoizedState),null;case 27:return mi(t),e===null&&he&&(a=t.stateNode=Sd(t.type,t.pendingProps,ae.current),Pe=t,Ct=!0,n=Re,Tl(t.type)?(br=n,Re=Dt(a.firstChild)):Re=n),Qe(e,t,t.pendingProps.children,l),zu(e,t),e===null&&(t.flags|=4194304),t.child;case 5:return e===null&&he&&((n=a=Re)&&(a=x0(a,t.type,t.pendingProps,Ct),a!==null?(t.stateNode=a,Pe=t,Re=Dt(a.firstChild),Ct=!1,n=!0):n=!1),n||Gl(t)),mi(t),n=t.type,u=t.pendingProps,c=e!==null?e.memoizedProps:null,a=u.children,yr(n,u)?a=null:c!==null&&yr(n,c)&&(t.flags|=32),t.memoizedState!==null&&(n=mc(e,t,Bm,null,null,l),zn._currentValue=n),zu(e,t),Qe(e,t,a,l),t.child;case 6:return e===null&&he&&((e=l=Re)&&(l=S0(l,t.pendingProps,Ct),l!==null?(t.stateNode=l,Pe=t,Re=null,e=!0):e=!1),e||Gl(t)),null;case 13:return vo(e,t,l);case 4:return Te(t,t.stateNode.containerInfo),a=t.pendingProps,e===null?t.child=Sa(t,null,a,l):Qe(e,t,a,l),t.child;case 11:return ro(e,t,t.type,t.pendingProps,l);case 7:return Qe(e,t,t.pendingProps,l),t.child;case 8:return Qe(e,t,t.pendingProps.children,l),t.child;case 12:return Qe(e,t,t.pendingProps.children,l),t.child;case 10:return a=t.pendingProps,rl(t,t.type,a.value),Qe(e,t,a.children,l),t.child;case 9:return n=t.type._context,a=t.pendingProps.children,Ql(t),n=We(n),a=a(n),t.flags|=1,Qe(e,t,a,l),t.child;case 14:return so(e,t,t.type,t.pendingProps,l);case 15:return fo(e,t,t.type,t.pendingProps,l);case 19:return bo(e,t,l);case 31:return a=t.pendingProps,l=t.mode,a={mode:a.mode,children:a.children},e===null?(l=Cu(a,l),l.ref=t.ref,t.child=l,l.return=t,t=l):(l=Xt(e.child,a),l.ref=t.ref,t.child=l,l.return=t,t=l),t;case 22:return oo(e,t,l);case 24:return Ql(t),a=We(we),e===null?(n=uc(),n===null&&(n=Ee,u=ac(),n.pooledCache=u,u.refCount++,u!==null&&(n.pooledCacheLanes|=l),n=u),t.memoizedState={parent:a,cache:n},cc(t),rl(t,we,n)):((e.lanes&l)!==0&&(rc(e,t),sn(t,null,null,l),rn()),n=e.memoizedState,u=t.memoizedState,n.parent!==a?(n={parent:a,cache:a},t.memoizedState=n,t.lanes===0&&(t.memoizedState=t.updateQueue.baseState=n),rl(t,we,a)):(a=u.cache,rl(t,we,a),a!==n.cache&&lc(t,[we],l,!0))),Qe(e,t,t.pendingProps.children,l),t.child;case 29:throw t.pendingProps}throw Error(s(156,t.tag))}function Wt(e){e.flags|=4}function So(e,t){if(t.type!=="stylesheet"||(t.state.loading&4)!==0)e.flags&=-16777217;else if(e.flags|=16777216,!Dd(t)){if(t=Et.current,t!==null&&((se&4194048)===se?_t!==null:(se&62914560)!==se&&(se&536870912)===0||t!==_t))throw un=ic,lf;e.flags|=8192}}function _u(e,t){t!==null&&(e.flags|=4),e.flags&16384&&(t=e.tag!==22?Fr():536870912,e.lanes|=t,Na|=t)}function gn(e,t){if(!he)switch(e.tailMode){case"hidden":t=e.tail;for(var l=null;t!==null;)t.alternate!==null&&(l=t),t=t.sibling;l===null?e.tail=null:l.sibling=null;break;case"collapsed":l=e.tail;for(var a=null;l!==null;)l.alternate!==null&&(a=l),l=l.sibling;a===null?t||e.tail===null?e.tail=null:e.tail.sibling=null:a.sibling=null}}function Ne(e){var t=e.alternate!==null&&e.alternate.child===e.child,l=0,a=0;if(t)for(var n=e.child;n!==null;)l|=n.lanes|n.childLanes,a|=n.subtreeFlags&65011712,a|=n.flags&65011712,n.return=e,n=n.sibling;else for(n=e.child;n!==null;)l|=n.lanes|n.childLanes,a|=n.subtreeFlags,a|=n.flags,n.return=e,n=n.sibling;return e.subtreeFlags|=a,e.childLanes=l,t}function Jm(e,t,l){var a=t.pendingProps;switch(Pi(t),t.tag){case 31:case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:return Ne(t),null;case 1:return Ne(t),null;case 3:return l=t.stateNode,a=null,e!==null&&(a=e.memoizedState.cache),t.memoizedState.cache!==a&&(t.flags|=2048),Kt(we),nl(),l.pendingContext&&(l.context=l.pendingContext,l.pendingContext=null),(e===null||e.child===null)&&(Pa(t)?Wt(t):e===null||e.memoizedState.isDehydrated&&(t.flags&256)===0||(t.flags|=1024,Fs())),Ne(t),null;case 26:return l=t.memoizedState,e===null?(Wt(t),l!==null?(Ne(t),So(t,l)):(Ne(t),t.flags&=-16777217)):l?l!==e.memoizedState?(Wt(t),Ne(t),So(t,l)):(Ne(t),t.flags&=-16777217):(e.memoizedProps!==a&&Wt(t),Ne(t),t.flags&=-16777217),null;case 27:Zn(t),l=ae.current;var n=t.type;if(e!==null&&t.stateNode!=null)e.memoizedProps!==a&&Wt(t);else{if(!a){if(t.stateNode===null)throw Error(s(166));return Ne(t),null}e=F.current,Pa(t)?$s(t):(e=Sd(n,a,l),t.stateNode=e,Wt(t))}return Ne(t),null;case 5:if(Zn(t),l=t.type,e!==null&&t.stateNode!=null)e.memoizedProps!==a&&Wt(t);else{if(!a){if(t.stateNode===null)throw Error(s(166));return Ne(t),null}if(e=F.current,Pa(t))$s(t);else{switch(n=Ku(ae.current),e){case 1:e=n.createElementNS("http://www.w3.org/2000/svg",l);break;case 2:e=n.createElementNS("http://www.w3.org/1998/Math/MathML",l);break;default:switch(l){case"svg":e=n.createElementNS("http://www.w3.org/2000/svg",l);break;case"math":e=n.createElementNS("http://www.w3.org/1998/Math/MathML",l);break;case"script":e=n.createElement("div"),e.innerHTML=" - + +
diff --git a/projeto_parte1.c b/projeto_parte1.c index 53aef51..b3d5c1a 100644 --- a/projeto_parte1.c +++ b/projeto_parte1.c @@ -1,2272 +1,2507 @@ . -// === Início de: main/main.c === -// === Início de: main/main.c === -#include +// === Início de: components/meter_manager/driver/meter_ade7758/meter_ade7758.h === +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include #include -#include - -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "freertos/event_groups.h" - -#include "esp_log.h" #include "esp_err.h" -#include "esp_event.h" -#include "esp_netif.h" -#include "esp_spiffs.h" -#include "esp_system.h" -#include "nvs_flash.h" -#include "driver/gpio.h" -#include "network.h" -#include "board_config.h" -#include "logger.h" -#include "rest_main.h" +/** + * @brief Inicializa o driver do medidor ADE7758 (SPI, mutex, registradores). + */ +esp_err_t meter_ade7758_init(void); -#include "peripherals.h" -#include "protocols.h" -#include "evse_manager.h" -#include "evse_core.h" -#include "auth.h" -#include "loadbalancer.h" -#include "meter_manager.h" -#include "buzzer.h" -#include "evse_link.h" -#include "ocpp.h" -#include "led.h" -#include "scheduler.h" +/** + * @brief Inicia a tarefa de leitura de dados do medidor ADE7758. + */ +esp_err_t meter_ade7758_start(void); -#define AP_CONNECTION_TIMEOUT 120000 -#define RESET_HOLD_TIME 30000 -#define DEBOUNCE_TIME_MS 50 +/** + * @brief Para a tarefa de leitura e limpa os dados internos do medidor ADE7758. + */ +void meter_ade7758_stop(void); -#define PRESS_BIT BIT0 -#define RELEASED_BIT BIT1 -static const char *TAG = "app_main"; - -static TaskHandle_t user_input_task = NULL; -static TickType_t press_tick = 0; -static volatile TickType_t last_interrupt_tick = 0; -static bool pressed = false; - -// -// File system (SPIFFS) init and info -// -static void fs_info(esp_vfs_spiffs_conf_t *conf) -{ - size_t total = 0, used = 0; - esp_err_t ret = esp_spiffs_info(conf->partition_label, &total, &used); - if (ret == ESP_OK) - ESP_LOGI(TAG, "Partition %s: total: %d, used: %d", conf->partition_label, total, used); - else - ESP_LOGE(TAG, "Failed to get SPIFFS info: %s", esp_err_to_name(ret)); +#ifdef __cplusplus } +#endif -static void fs_init(void) -{ - esp_vfs_spiffs_conf_t cfg_conf = { - .base_path = "/cfg", - .partition_label = "cfg", - .max_files = 1, - .format_if_mount_failed = false}; - - esp_vfs_spiffs_conf_t data_conf = { - .base_path = "/data", - .partition_label = "data", - .max_files = 5, - .format_if_mount_failed = true}; - - ESP_ERROR_CHECK(esp_vfs_spiffs_register(&cfg_conf)); - ESP_ERROR_CHECK(esp_vfs_spiffs_register(&data_conf)); - - fs_info(&cfg_conf); - fs_info(&data_conf); -} - -// -// Wi-Fi event monitoring task -// -static void wifi_event_task_func(void *param) -{ - EventBits_t mode_bits; - for (;;) - { - mode_bits = xEventGroupWaitBits( - wifi_event_group, - WIFI_AP_MODE_BIT | WIFI_STA_MODE_BIT, - pdFALSE, - pdFALSE, - portMAX_DELAY); - - if (mode_bits & WIFI_AP_MODE_BIT) - { - if (xEventGroupWaitBits( - wifi_event_group, - WIFI_AP_CONNECTED_BIT, - pdFALSE, - pdFALSE, - pdMS_TO_TICKS(AP_CONNECTION_TIMEOUT)) & - WIFI_AP_CONNECTED_BIT) - { - xEventGroupWaitBits( - wifi_event_group, - WIFI_AP_DISCONNECTED_BIT, - pdFALSE, - pdFALSE, - portMAX_DELAY); - } - else - { - if (xEventGroupGetBits(wifi_event_group) & WIFI_AP_MODE_BIT) - { - // wifi_ap_stop(); - } - } - } - else if (mode_bits & WIFI_STA_MODE_BIT) - { - xEventGroupWaitBits( - wifi_event_group, - WIFI_STA_DISCONNECTED_BIT, - pdFALSE, - pdFALSE, - portMAX_DELAY); - } - } -} - -// -// Button press handler -// -static void handle_button_press(void) -{ - if (!(xEventGroupGetBits(wifi_event_group) & WIFI_AP_MODE_BIT)) - { - ESP_LOGI(TAG, "Starting Wi-Fi AP mode"); - wifi_ap_start(); - } -} - -// Task to handle button press/release notifications -static void user_input_task_func(void *param) -{ - uint32_t notification; - for (;;) - { - if (xTaskNotifyWait( - 0, - UINT32_MAX, - ¬ification, - portMAX_DELAY)) - { - if (notification & PRESS_BIT) - { - press_tick = xTaskGetTickCount(); - pressed = true; - ESP_LOGI(TAG, "Button Pressed"); - handle_button_press(); - } - - if ((notification & RELEASED_BIT) && pressed) - { - pressed = false; - TickType_t held = xTaskGetTickCount() - press_tick; - ESP_LOGI(TAG, "Button Released (held %u ms)", (unsigned)pdTICKS_TO_MS(held)); - - if (held >= pdMS_TO_TICKS(RESET_HOLD_TIME)) - { - ESP_LOGW(TAG, "Long press: erasing NVS + reboot"); - nvs_flash_erase(); - esp_restart(); - } - } - } - } -} - -// ISR for button GPIO interrupt (active-low) -static void IRAM_ATTR button_isr_handler(void *arg) -{ - BaseType_t higher_task_woken = pdFALSE; - TickType_t now = xTaskGetTickCountFromISR(); - - if (now - last_interrupt_tick < pdMS_TO_TICKS(DEBOUNCE_TIME_MS)) - { - return; - } - last_interrupt_tick = now; - - if (user_input_task == NULL) - { - return; - } - - int level = gpio_get_level(board_config.button_wifi_gpio); - if (level == 0) - { - xTaskNotifyFromISR( - user_input_task, - PRESS_BIT, - eSetBits, - &higher_task_woken); - } - else - { - xTaskNotifyFromISR( - user_input_task, - RELEASED_BIT, - eSetBits, - &higher_task_woken); - } - - if (higher_task_woken) - { - portYIELD_FROM_ISR(); - } -} - -static void button_init(void) -{ - gpio_config_t conf = { - .pin_bit_mask = BIT64(board_config.button_wifi_gpio), - .mode = GPIO_MODE_INPUT, - .pull_down_en = GPIO_PULLDOWN_DISABLE, - .pull_up_en = GPIO_PULLUP_ENABLE, - .intr_type = GPIO_INTR_ANYEDGE}; - - ESP_ERROR_CHECK(gpio_config(&conf)); - ESP_ERROR_CHECK(gpio_isr_handler_add(board_config.button_wifi_gpio, button_isr_handler, NULL)); -} - -// -// Inicialização dos módulos do sistema (SEM botão) -// -static void init_modules(void) -{ - peripherals_init(); - led_init(); - wifi_ini(); - buzzer_init(); - ESP_ERROR_CHECK(rest_server_init("/data")); - protocols_init(); - evse_manager_init(); - evse_init(); - auth_init(); - loadbalancer_init(); - meter_manager_init(); - meter_manager_start(); - evse_link_init(); - ocpp_start(); - scheduler_init(); -} - -// -// Função principal do firmware -// -void app_main(void) -{ - logger_init(); - esp_log_set_vprintf(logger_vprintf); - - esp_reset_reason_t reason = esp_reset_reason(); - ESP_LOGI(TAG, "Reset reason: %d", reason); - - esp_err_t ret = nvs_flash_init(); - if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) - { - ESP_LOGW(TAG, "Erasing NVS flash"); - ESP_ERROR_CHECK(nvs_flash_erase()); - ret = nvs_flash_init(); - } - ESP_ERROR_CHECK(ret); - - fs_init(); - ESP_ERROR_CHECK(esp_netif_init()); - ESP_ERROR_CHECK(esp_event_loop_create_default()); - ESP_ERROR_CHECK(gpio_install_isr_service(0)); - - board_config_load(); - - // 1) cria a task que recebe notificações do botão - xTaskCreate(user_input_task_func, "user_input_task", 4 * 1024, NULL, 3, &user_input_task); - - // 2) agora é seguro registrar ISR do botão - button_init(); - - // 3) inicia o resto do sistema - init_modules(); - - // 4) tasks auxiliares - xTaskCreate(wifi_event_task_func, "wifi_event_task", 8 * 1024, NULL, 3, NULL); -} - -// === Fim de: main/main.c === - -// === Fim de: main/main.c === +// === Fim de: components/meter_manager/driver/meter_ade7758/meter_ade7758.h === -// === Início de: components/rest_api/src/ocpp_api.c === -// ========================= -// ocpp_api.c -// ========================= -#include "ocpp.h" -#include "esp_log.h" +// === Início de: components/meter_manager/driver/meter_ade7758/ade7758.h === +#include "driver/spi_common.h" +#include "driver/spi_master.h" + + + +#define WRITE 0x80 // WRITE bit BT7 to write to registers +#define CLKIN 10000000 // ADE7758 frec, 10.000000MHz +#define PERIODO 50 // Actually it is frequency, it is used to calculate the amount of Cycles that it accumulates for energy. +#define PHASE_A 1 +#define PHASE_B 2 +#define PHASE_C 3 + + +//Register address + +//------Name--------Address---------Lenght +#define AWATTHR 0x01 //---------16 +#define BWATTHR 0x02 //---------16 +#define CWATTHR 0x03 //---------16 + +#define AVARHR 0x04 //---------16 +#define BVARHR 0x05 //---------16 +#define CVARHR 0x06 //---------16 + +#define AVAHR 0x07 //---------16 +#define BVAHR 0x08 //---------16 +#define CVAHR 0x09 //---------16 + +#define AIRMS 0x0A //---------24 +#define BIRMS 0x0B //---------24 +#define CIRMS 0x0C //---------24 + +#define AVRMS 0x0D //---------24 +#define BVRMS 0x0E //---------24 +#define CVRMS 0x0F //---------24 + +#define FREQ 0x10 //---------12 +#define TEMP 0x11 //---------8 +#define WFORM 0x12 //---------24 +#define OPMODE 0x13 //---------8 +#define MMODE 0x14 //---------8 +#define WAVMODE 0x15 //---------8 +#define COMPMODE 0x16 //---------8 +#define LCYCMODE 0x17 //---------8 +#define MASK 0x18 //---------24 +#define STATUS 0x19 //---------24 +#define RSTATUS 0x1A //---------24 +#define ZXTOUT 0x1B //---------16 +#define LINECYC 0x1C //---------16 +#define SAGCYC 0x1D //---------8 +#define SAGLVL 0x1E //---------8 +#define VPINTLVL 0x1F //---------8 +#define IPINTLVL 0x20 //---------8 +#define VPEAK 0x21 //---------8 +#define IPEAK 0x22 //---------8 +#define GAIN 0x23 //---------8 +#define AVRMSGAIN 0x24 //---------12 +#define BVRMSGAIN 0x25 //---------12 +#define CVRMSGAIN 0x26 //---------12 +#define AIGAIN 0x27 //---------12 +#define BIGAIN 0x28 //---------12 +#define CIGAIN 0x29 //---------12 +#define AWG 0x2A //---------12 +#define BWG 0x2B //---------12 +#define CWG 0x2C //---------12 +#define AVARG 0x2D //---------12 +#define BVARG 0x2E //---------12 +#define CVARG 0x2F //---------12 +#define AVAG 0x30 //---------12 +#define BVAG 0X31 //---------12 +#define CVAG 0x32 //---------12 +#define AVRMSOS 0x33 //---------12 +#define BVRMSOS 0X34 //---------12 +#define CVRMSOS 0X35 //---------12 +#define AIRMSOS 0X36 //---------12 +#define BIRMSOS 0X37 //---------12 +#define CIRMSOS 0X38 //---------12 +#define AWATTOS 0X39 //---------12 +#define BWATTOS 0X3A //---------12 +#define CWATTOS 0X3B //---------12 +#define AVAROS 0X3C //---------12 +#define BVAROS 0X3D //---------12 +#define CVAROS 0X3E //---------12 +#define APHCAL 0X3F //---------7 +#define BPHCAL 0X40 //---------7 +#define CPHCAL 0X41 //---------7 +#define WDIV 0X42 //---------8 +#define VARDIV 0X43 //---------8 +#define VADIV 0X44 //---------8 +#define APCFNUM 0X45 //---------16 +#define APCFDEN 0X46 //---------12 +#define VARCFNUM 0X47 //---------16 +#define VARCFDEN 0X48 //---------12 + +#define CHKSUM 0X7E //---------8 +#define VERSION 0x7f //---------8 +#define DUMMY_BYTE 0xFF + + +//bits + +/** +OPERATIONAL MODE REGISTER (0x13) +The general configuration of the ADE7758 is defined by writing to the OPMODE register. +Table 18 summarizes the functionality of each bit in the OPMODE register. + +Bit Location Bit Mnemonic Default Value Description +0 DISHPF 0 The HPFs in all current channel inputs are disabled when this bit is set. +1 DISLPF 0 The LPFs after the watt and VAR multipliers are disabled when this bit is set. +2 DISCF 1 The frequency outputs APCF and VARCF are disabled when this bit is set. +3 to 5 DISMOD 0 By setting these bits, ADE7758�s ADCs can be turned off. In normal operation, these bits should be left at Logic 0. + DISMOD[2:0] Description + 0 0 0 Normal operation. + 1 0 0 Redirect the voltage inputs to the signal paths for the current channels and the current inputs to the signal paths for the voltage channels. + 0 0 1 Switch off only the current channel ADCs. + 1 0 1 Switch off current channel ADCs and redirect the current input signals to the voltage channel signal paths. + 0 1 0 Switch off only the voltage channel ADCs. + 1 1 0 Switch off voltage channel ADCs and redirect the voltage input signals to the current channel signal paths. + 0 1 1 Put the ADE7758 in sleep mode. + 1 1 1 Put the ADE7758 in power-down mode (reduces AIDD to 1 mA typ). +6 SWRST 0 Software Chip Reset. A data transfer to the ADE7758 should not take place for at least 18 �s after a software reset. +7 RESERVED 0 This should be left at 0. + +*/ + +#define DISHPF 0x01 +#define DISLPF 0x02 +#define DISCF 0x04 +#define SWRST 0x40 + +/** +MEASUREMENT MODE REGISTER (0x14) +The configuration of the PERIOD and peak measurements made by the ADE7758 is defined by writing to the MMODE register. +Table 19 summarizes the functionality of each bit in the MMODE register. + +Bit Location Bit Mnemonic Default Value Description +0 to 1 FREQSEL 0 These bits are used to select the source of the measurement of the voltage line frequency. + FREQSEL1 FREQSEL0 Source + 0 0 Phase A + 0 1 Phase B + 1 0 Phase C + 1 1 Reserved +2 to 4 PEAKSEL 7 These bits select the phases used for the voltage and current peak registers. + Setting Bit 2 switches the IPEAK and VPEAK registers to hold the absolute values + of the largest current and voltage waveform (over a fixed number of half-line cycles) + from Phase A. The number of half-line cycles is determined by the content of the + LINECYC register. At the end of the LINECYC number of half-line cycles, the content + of the registers is replaced with the new peak values. Similarly, setting Bit 3 turns + on the peak detection for Phase B, and Bit 4 for Phase C. Note that if more than one + bit is set, the VPEAK and IPEAK registers can hold values from two different phases, that is, + the voltage and current peak are independently processed (see the Peak Current Detection section). +5 to 7 PKIRQSEL 7 These bits select the phases used for the peak interrupt detection. + Setting Bit 5 switches on the monitoring of the absolute current and voltage waveform to Phase A. + Similarly, setting Bit 6 turns on the waveform detection for Phase B, and Bit 7 for Phase C. + Note that more than one bit can be set for detection on multiple phases. + If the absolute values of the voltage or current waveform samples in the selected phases exceeds + the preset level specified in the VPINTLVL or IPINTLVL registers the corresponding bit(s) in the + STATUS registers are set (see the Peak Current Detection section). + +*/ + +#define FREQSEL0 0x01 +#define FREQSEL1 0x02 + + +/** +WAVEFORM MODE REGISTER (0x15) +The waveform sampling mode of the ADE7758 is defined by writing to the WAVMODE register. +Table 20 summarizes the functionality of each bit in the WAVMODE register. + +Bit Location Bit Mnemonic Default Value Description +0 to 1 PHSEL 0 These bits are used to select the phase of the waveform sample. + PHSEL[1:0] Source + 0 0 Phase A + 0 1 Phase B + 1 0 Phase C + 1 1 Reserved +2 to 4 WAVSEL 0 These bits are used to select the type of waveform. + WAVSEL[2:0] Source + 0 0 0 Current + 0 0 1 Voltage + 0 1 0 Active Power Multiplier Output + 0 1 1 Reactive Power Multiplier Output + 1 0 0 VA Multiplier Output + -Others- Reserved +5 to 6 DTRT 0 These bits are used to select the data rate. + DTRT[1:0] Update Rate + 0 0 26.04 kSPS (CLKIN/3/128) + 0 1 13.02 kSPS (CLKIN/3/256) + 1 0 6.51 kSPS (CLKIN/3/512) + 1 1 3.25 kSPS (CLKIN/3/1024) +7 VACF 0 Setting this bit to Logic 1 switches the VARCF output pin to an output + frequency that is proportional to the total apparent power (VA). + In the default state, Logic 0, the VARCF pin outputs a frequency proportional + to the total reactive power (VAR). +*/ + + + +/** +COMPUTATIONAL MODE REGISTER (0x16) +The computational method of the ADE7758 is defined by writing to the COMPMODE register. + +Bit Location Bit Mnemonic Default Value Description +0 to 1 CONSEL 0 These bits are used to select the input to the energy accumulation registers. + CONSEL[1:0] = 11 is reserved. IA, IB, and IC are IA, IB, and IC phase shifted by �90�, respectively. + Registers CONSEL[1, 0] = 00 CONSEL[1, 0] = 01 CONSEL[1, 0] = 10 + AWATTHR VA � IA VA � (IA � IB) VA � (IA�IB) + BWATTHR VB � IB 0 0 + CWATTHR VC � IC VC � (IC � IB) VC � IC + + AVARHR VA � IA VA � (IA � IB) VA � (IA�IB) + BVARHR VB � IB 0 0 + CVARHR VC � IC VC � (IC � IB) VC � IC + + AVAHR VARMS � IARMS VARMS � IARMS VARMS � ARMS + BVAHR VBRMS � IBRMS (VARMS + VCRMS)/2 � IBRMS VARMS � IBRMS + CVAHR VCRMS � ICRMS VCRMS � ICRMS VCRMS � ICRMS + +2 to 4 TERMSEL 7 These bits are used to select the phases to be included in the APCF and VARCF pulse outputs. + Setting Bit 2 selects Phase A (the inputs to AWATTHR and AVARHR registers) to be included. + Bit 3 and Bit 4 are for Phase B and Phase C, respectively. + Setting all three bits enables the sum of all three phases to be included in the frequency outputs + (see the Active Power Frequency Output and the Reactive Power Frequency Output sections). + +5 ABS 0 Setting this bit places the APCF output pin in absolute only mode. + Namely, the APCF output frequency is proportional to the sum of the absolute values of the watt-hour + accumulation registers (AWATTHR, BWATTHR, and CWATTHR). + Note that this bit only affects the APCF pin and has no effect on the content of the corresponding + registers. + +6 SAVAR 0 Setting this bit places the VARCF output pin in the signed adjusted mode. + Namely, the VARCF output frequency is proportional to the sign-adjusted sum of the VAR-hour accumulation + registers (AVARHR, BVARHR, and CVARHR). + The sign of the VAR is determined from the sign of the watt calculation from the corresponding phase, + that is, the sign of the VAR is flipped if the sign of the watt is negative, and if the watt is positive, + there is no change to the sign of the VAR. + Note that this bit only affects the VARCF pin and has no effect on the content of the corresponding + registers. + +7 NOLOAD 0 Setting this bit activates the no-load threshold in the ADE7758. +*/ + + +/** +LINE CYCLE ACCUMULATION MODE REGISTER (0x17) +The functionalities involved the line-cycle accumulation mode in the ADE7758 are defined by writing to the LCYCMODE register. + +Bit Location Bit Mnemonic Default Value Description + +0 LWATT 0 Setting this bit places the watt-hour accumulation registers + (AWATTHR, BWATTHR, and CWATTHR registers) into line-cycle accumulation mode. +1 LVAR 0 Setting this bit places the VAR-hour accumulation registers (AVARHR, BVARHR, and CVARHR registers) + into line-cycle accumulation mode. +2 LVA 0 Setting this bit places the VA-hour accumulation registers (AVAHR, BVAHR, and CVAHR registers) + into line-cycle accumulation mode. +3 to 5 ZXSEL 7 These bits select the phases used for counting the number of zero crossings in the line-cycle + accumulation mode. Bit 3, Bit 4, and Bit 5 select Phase A, Phase B, and Phase C, respectively. + More than one phase can be selected for the zero-crossing detection, + and the accumulation time is shortened accordingly. +6 RSTREAD 1 Setting this bit enables the read-with-reset for all the WATTHR, VARHR, and VAHR registers for all three + phases, that is, a read to those registers resets the registers to 0 after the content of the registers + have been read. This bit should be set to Logic 0 when the LWATT, LVAR, or LVA bits are set to Logic 1. +7 FREQSEL 0 Setting this bit causes the FREQ (0x10) register to display the period, instead of the frequency of the + line input. +*/ + + +#define LWATT 0x01 +#define LVAR 0x02 +#define LVA 0x04 +#define ZXSEL_A 0x08 +#define ZXSEL_B 0x10 +#define ZXSEL_C 0x20 +#define RSTREAD 0x40 +#define FREQSEL 0x80 + + + +/** INTERRUPT MASK REGISTER (0x18) +When an interrupt event occurs in the ADE7758, the IRQ logic output goes active low if the mask bit for this event is Logic 1 in the MASK register. +The IRQ logic output is reset to its default collector open state when the RSTATUS register is read. +describes the function of each bit in the interrupt mask register. +**/ + +// The next table summarizes the function of each bit for +// the Interrupt Enable Register + +/* Bit Mask // Bit Location / Description +#define AEHF 0x0001 // bit 0 - Enables an interrupt when there is a change in Bit 14 of any one of the three WATTHR registers, that is, the WATTHR register is half full. +#define REHF 0x0002 // bit 1 - Enables an interrupt when there is a change in Bit 14 of any one of the three VARHR registers, that is, the VARHR register is half full. +#define VAEHF 0x0004 // bit 2 - Enables an interrupt when there is a 0 to 1 transition in the MSB of any one of the three VAHR registers, that is, the VAHR register is half full. +#define SAGA 0x0008 // bit 3 - Enables an interrupt when there is a SAG on the line voltage of the Phase A. +#define SAGB 0x0010 // bit 4 - Enables an interrupt when there is a SAG on the line voltage of the Phase B. +#define SAGC 0x0020 // bit 5 - Enables an interrupt when there is a SAG on the line voltage of the Phase C. +#define ZXTOA 0x0040 // bit 6 - Enables an interrupt when there is a zero-crossing timeout detection on Phase A. +#define ZXTOB 0x0080 // bit 7 - Enables an interrupt when there is a zero-crossing timeout detection on Phase B. +#define ZXTOC 0x0100 // bit 8 - Enables an interrupt when there is a zero-crossing timeout detection on Phase C. +#define ZXA 0x0200 // bit 9 - Enables an interrupt when there is a zero crossing in the voltage channel of Phase A +#define ZXB 0x0400 // bit 10 - Enables an interrupt when there is a zero crossing in the voltage channel of Phase B +#define ZXC 0x0800 // bit 11 - Enables an interrupt when there is a zero crossing in the voltage channel of Phase C +#define LENERGY 0x1000 // bit 12 - Enables an interrupt when the energy accumulations over LINECYC are finished. +//RESERVED 0x2000 // bit 13 - RESERVED +#define PKV 0x4000 // bit 14 - Enables an interrupt when the voltage input selected in the MMODE register is above the value in the VPINTLVL register. +#define PKI 0x8000 // bit 15 - Enables an interrupt when the current input selected in the MMODE register is above the value in the IPINTLVL register. +#define WFSM 0x010000 // bit 16 - Enables an interrupt when data is present in the WAVEMODE register. +#define REVPAP 0x020000 // bit 17 - Enables an interrupt when there is a sign change in the watt calculation among any one of the phases specified by the TERMSEL bits in the COMPMODE register. +#define REVPRP 0x040000 // bit 18 - Enables an interrupt when there is a sign change in the VAR calculation among any one of the phases specified by the TERMSEL bits in the COMPMODE register. +#define SEQERR 0x080000 // bit 19 - Enables an interrupt when the zero crossing from Phase A is followed not by the zero crossing of Phase C but with that of Phase B. +*/ +/** INTERRUPT STATUS REGISTER (0x19)/RESET INTERRUPT STATUS REGISTER (0x1A) +The interrupt status register is used to determine the source of an interrupt event. +When an interrupt event occurs in the ADE7758, the corresponding flag in the interrupt status register is set. +The IRQ pin goes active low if the corresponding bit in the interrupt mask register is set. +When the MCU services the interrupt, it must first carry out a read from the interrupt status register to determine the source of the interrupt. +All the interrupts in the interrupt status register stay at their logic high state after an event occurs. +The state of the interrupt bit in the interrupt status register is reset to its default value once the reset interrupt status register is read. +**/ + +// The next table summarizes the function of each bit for +// the Interrupt Status Register, the Reset Interrupt Status Register. + +// Bit Mask // Bit Location / Description +#define AEHF 0x0001 // bit 0 - Indicates that an interrupt was caused by a change in Bit 14 among any one of the three WATTHR registers, that is, the WATTHR register is half full. +#define REHF 0x0002 // bit 1 - Indicates that an interrupt was caused by a change in Bit 14 among any one of the three VARHR registers, that is, the VARHR register is half full. +#define VAEHF 0x0004 // bit 2 - Indicates that an interrupt was caused by a 0 to 1 transition in Bit 15 among any one of the three VAHR registers, that is, the VAHR register is half full. +#define SAGA 0x0008 // bit 3 - Indicates that an interrupt was caused by a SAG on the line voltage of the Phase A. +#define SAGB 0x0010 // bit 4 - Indicates that an interrupt was caused by a SAG on the line voltage of the Phase B. +#define SAGC 0x0020 // bit 5 - Indicates that an interrupt was caused by a SAG on the line voltage of the Phase C. +#define ZXTOA 0x0040 // bit 6 - Indicates that an interrupt was caused by a missing zero crossing on the line voltage of the Phase A. +#define ZXTOB 0x0080 // bit 7 - Indicates that an interrupt was caused by a missing zero crossing on the line voltage of the Phase B. +#define ZXTOC 0x0100 // bit 8 - Indicates that an interrupt was caused by a missing zero crossing on the line voltage of the Phase C +#define ZXA 0x0200 // bit 9 - Indicates a detection of a rising edge zero crossing in the voltage channel of Phase A. +#define ZXB 0x0400 // bit 10 - Indicates a detection of a rising edge zero crossing in the voltage channel of Phase B +#define ZXC 0x0800 // bit 11 - Indicates a detection of a rising edge zero crossing in the voltage channel of Phase C +#define LENERGY 0x1000 // bit 12 - In line energy accumulation, indicates the end of an integration over an integer number of half- line cycles (LINECYC). See the Calibration section. +#define RESET 0x2000 // bit 13 - Indicates that the 5 V power supply is below 4 V. Enables a software reset of the ADE7758 and sets the registers back to their default values. This bit in the STATUS or RSTATUS register is logic high for only one clock cycle after a reset event. +#define PKV 0x4000 // bit 14 - Indicates that an interrupt was caused when the selected voltage input is above the value in the VPINTLVL register. +#define PKI 0x8000 // bit 15 - Indicates that an interrupt was caused when the selected current input is above the value in the IPINTLVL register. +#define WFSM 0x010000 // bit 16 - Indicates that new data is present in the waveform register. +#define REVPAP 0x020000 // bit 17 - Indicates that an interrupt was caused by a sign change in the watt calculation among any one of the phases specified by the TERMSEL bits in the COMPMODE register. +#define REVPRP 0x040000 // bit 18 - Indicates that an interrupt was caused by a sign change in the VAR calculation among any one of the phases specified by the TERMSEL bits in the COMPMODE register. +#define SEQERR 0x080000 // bit 19 - Indicates that an interrupt was caused by a zero crossing from Phase A followed not by the zero crossing of Phase C but by that of Phase B. + + +//constants +#define GAIN_1 0x00 +#define GAIN_2 0x01 +#define GAIN_4 0x02 +#define INTEGRATOR_ON 1 +#define INTEGRATOR_OFF 0 +#define FULLSCALESELECT_0_5V 0x00 +#define FULLSCALESELECT_0_25V 0x01 +#define FULLSCALESELECT_0_125V 0x02 + +esp_err_t transferByte(const uint8_t reg_addr, const uint8_t data, const uint8_t command); +esp_err_t transferMultiplesBytes(const uint8_t reg_addr, uint8_t *tx_buf, uint8_t *rx_buf, size_t data_length, const uint8_t command); + +esp_err_t Init(const spi_host_device_t spi_peripheral, const int pin_miso, const int pin_mosi, const int pin_sclk); +esp_err_t InitSpi(const int ss); + +esp_err_t RegisterDevice(const uint8_t mode, const int ss, const int addr_length, const int command_length, const int bus_speed); +uint8_t ReadRegister(const uint8_t reg_addr, const uint8_t command); +esp_err_t WriteRegister(const uint8_t reg_addr, const uint8_t reg_data, const uint8_t command); +esp_err_t WriteRegisterMultipleBytes(const uint8_t reg_addr, uint8_t *reg_data_buffer, const uint8_t byte_count, const uint8_t command); +esp_err_t ReadRegisterMultipleBytes(const uint8_t reg_addr, uint8_t *reg_data_buffer, const uint8_t byte_count, const uint8_t command); +spi_device_handle_t GetHandle(); + + +//---------------------------------------------------------------------------- +// Modes and configurations +//---------------------------------------------------------------------------- +void setOpMode(uint8_t m); +uint8_t getOpMode(); +void setMMode(uint8_t m); +uint8_t getMMode(); +void setWavMode(uint8_t m); +uint8_t getWavMode(); +void setCompMode(uint8_t m); +uint8_t getCompMode(); +void setLcycMode(uint8_t m); +uint8_t getLcycMode(); +void gainSetup(uint8_t integrator, uint8_t scale, uint8_t PGA2, uint8_t PGA1); +void setupDivs(uint8_t Watt_div,uint8_t VAR_div,uint8_t VA_div); +uint32_t getMaskInterrupts(); +void setMaskInterrupts(uint32_t m); +uint32_t getStatus(); +uint32_t resetStatus(); +int32_t getAIRMS(); +int32_t getBIRMS(); +int32_t getCIRMS(); +int32_t getAVRMS(); +int32_t getBVRMS(); +int32_t getCVRMS(); +uint32_t avrms(); +uint32_t bvrms(); +uint32_t cvrms(); +uint32_t airms(); +uint32_t birms(); +int32_t cirms(); +int32_t getFreq(); +void setLineCyc(uint32_t d); +int32_t getACurrentOffset(); +int32_t getBCurrentOffset(); +int32_t getCCurrentOffset(); +void setACurrentOffset(int32_t o); +void setBCurrentOffset(int32_t o); +void setCCurrentOffset(int32_t o); +int32_t getAVoltageOffset(); +int32_t getBVoltageOffset(); +int32_t getCVoltageOffset(); +void setAVoltageOffset(int32_t o); +void setBVoltageOffset(int32_t o); +void setCVoltageOffset(int32_t o); +void setAWattOffset(int32_t o); +void setBWattOffset(int32_t o); +void setCWattOffset(int32_t o); +void setZeroCrossingTimeout(int32_t d); +int32_t getZeroCrossingTimeout(); +uint8_t setPotLine(uint8_t Phase, uint32_t Ciclos); +int32_t getWatt(uint8_t Phase); +int32_t getVar(uint8_t Phase); +int32_t getVa(uint8_t Phase); +uint8_t getVersion(); +uint8_t read8(uint8_t reg); +uint32_t read16(uint8_t reg); +uint32_t read24(uint8_t reg); + +esp_err_t write24(uint8_t reg, uint32_t data); +esp_err_t write16(uint8_t reg, uint32_t data); +esp_err_t write8(uint8_t reg, uint8_t data); + +void enableADE7758Chip(); +void disableADE7758Chip(); +void setAPCFDEN(int32_t d); +int32_t getAPCFDEN(); +void setAPCFNUM(int32_t d); +int32_t getAPCFNUM(); +void setVARCFNUM(int32_t d); +int32_t getVARCFNUM(); +void setVARCFDEN(int32_t d); +int32_t getVARCFDEN(); +void setAWG(int32_t d); +int32_t getAWG(); +void setBWG(int32_t d); +void setCWG(int32_t d); +void setAVARG(int32_t d); +int32_t getAVARG(); +void setBVARG(int32_t d); +int32_t getBVARG(); +void setCVARG(int32_t d); +int32_t getCVARG(); +void setAVAG(int32_t d); +void setBVAG(int32_t d); +void setCVAG(int32_t d); +// === Fim de: components/meter_manager/driver/meter_ade7758/ade7758.h === + + +// === Início de: components/meter_manager/driver/meter_orno/modbus_params.h === +/* + * SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _DEVICE_PARAMS +#define _DEVICE_PARAMS + +#include + +#pragma pack(push, 1) + +// Discrete Inputs +typedef struct { + uint8_t discrete_input0 : 1; + uint8_t discrete_input1 : 1; + uint8_t discrete_input2 : 1; + uint8_t discrete_input3 : 1; + uint8_t discrete_input4 : 1; + uint8_t discrete_input5 : 1; + uint8_t discrete_input6 : 1; + uint8_t discrete_input7 : 1; + uint8_t discrete_input_port1; + uint8_t discrete_input_port2; +} discrete_reg_params_t; + +// Coils +typedef struct { + uint8_t coils_port0; + uint8_t coils_port1; + uint8_t coils_port2; +} coil_reg_params_t; + +// Input Registers (pode manter caso use em outro driver) +typedef struct { + float input_data0; + float input_data1; + float input_data2; + float input_data3; + uint16_t data[150]; + float input_data4; + float input_data5; + float input_data6; + float input_data7; + uint16_t data_block1[150]; +} input_reg_params_t; + + +// Holding Registers (ajustado para os campos usados no ORNO 516) +typedef struct { + float l1_current; + float l2_current; + float l3_current; + + float l1_voltage; + float l2_voltage; + float l3_voltage; + + float active_energy; + float reactive_energy; + + float active_power; + float apparent_power; + float reactive_power; + + float frequency; + float power_factor; +} holding_reg_params_t; + + + +#pragma pack(pop) + +// Instâncias globais das estruturas +extern holding_reg_params_t holding_reg_params; +extern input_reg_params_t input_reg_params; +extern coil_reg_params_t coil_reg_params; +extern discrete_reg_params_t discrete_reg_params; + +#endif // !_DEVICE_PARAMS + +// === Fim de: components/meter_manager/driver/meter_orno/modbus_params.h === + + +// === Início de: components/meter_manager/driver/meter_orno/meter_orno.h === +#ifndef ORNO_MODBUS_H_ +#define ORNO_MODBUS_H_ + +#include #include "esp_err.h" -#include "esp_http_server.h" -#include "cJSON.h" -#include -static const char *TAG = "ocpp_api"; -static esp_err_t ocpp_get_status_handler(httpd_req_t *req) { - ESP_LOGD(TAG, "GET /api/v1/ocpp"); - httpd_resp_set_type(req, "application/json"); +#include +#include +#include "esp_err.h" - char server[64] = {0}; - char charge_id[64] = {0}; - ocpp_get_server(server); - ocpp_get_charge_id(charge_id); +/** + * @brief Inicializa o driver do medidor (SPI, mutex, registradores ADE7758). + */ +esp_err_t meter_init(void); - cJSON *status = cJSON_CreateObject(); - cJSON_AddBoolToObject(status, "connected", ocpp_is_connected()); - cJSON_AddStringToObject(status, "server", server); - cJSON_AddStringToObject(status, "charge_id", charge_id); +/** + * @brief Inicia a tarefa de leitura de dados do medidor. + */ +esp_err_t meter_start(void); - char *str = cJSON_PrintUnformatted(status); - httpd_resp_sendstr(req, str); +/** + * @brief Para a tarefa de leitura e limpa os dados internos. + */ +void meter_stop(void); - free(str); - cJSON_Delete(status); - return ESP_OK; +/** + * @brief Verifica se o medidor está em execução. + * + * @return true se a tarefa estiver ativa, false caso contrário. + */ +bool meter_is_running(void); + +/** + * @brief Limpa os dados armazenados no medidor (zera todos os valores). + */ +void meter_clear_data(void); + +// ----- Leituras por fase (L1, L2, L3) ----- + +// Tensão RMS (em volts) +float meter_get_vrms_l1(void); +float meter_get_vrms_l2(void); +float meter_get_vrms_l3(void); + +// Corrente RMS (em amperes) +float meter_get_irms_l1(void); +float meter_get_irms_l2(void); +float meter_get_irms_l3(void); + +// Potência ativa (W) +int meter_get_watt_l1(void); +int meter_get_watt_l2(void); +int meter_get_watt_l3(void); + +// Potência reativa (VAR) +int meter_get_var_l1(void); +int meter_get_var_l2(void); +int meter_get_var_l3(void); + +// Potência aparente (VA) +int meter_get_va_l1(void); +int meter_get_va_l2(void); +int meter_get_va_l3(void); + +// (Opcional) contador de watchdog para diagnóstico +uint32_t meter_get_watchdog_counter(void); + +#ifdef __cplusplus } +#endif -static esp_err_t ocpp_get_config_handler(httpd_req_t *req) { - ESP_LOGD(TAG, "GET /api/v1/config/ocpp"); - httpd_resp_set_type(req, "application/json"); +#endif /* ORNO_MODBUS_H_ */ - char server[64] = {0}; - char charge_id[64] = {0}; - bool enabled = ocpp_get_enabled(); - ocpp_get_server(server); - ocpp_get_charge_id(charge_id); +// === Fim de: components/meter_manager/driver/meter_orno/meter_orno.h === - cJSON *json = cJSON_CreateObject(); - cJSON_AddBoolToObject(json, "enabled", enabled); - cJSON_AddStringToObject(json, "url", server); - cJSON_AddStringToObject(json, "chargeBoxId", charge_id); - char *str = cJSON_PrintUnformatted(json); - httpd_resp_sendstr(req, str); +// === Início de: components/meter_manager/driver/meter_orno/meter_ea777.h === +#ifndef METER_DTS6619_H_ +#define METER_DTS6619_H_ - free(str); - cJSON_Delete(json); - return ESP_OK; +#include +#include +#include "esp_err.h" + +/** + * @brief Inicializa o driver do medidor dts6619 (SPI, mutex, registradores). + * + * @return esp_err_t Retorna ESP_OK se a inicialização for bem-sucedida, caso contrário retorna um erro. + */ +esp_err_t meter_dts6619_init(void); + +/** + * @brief Inicia a tarefa de leitura de dados do medidor DTS6619. + * + * @return esp_err_t Retorna ESP_OK se a tarefa for iniciada com sucesso, caso contrário retorna um erro. + */ +esp_err_t meter_dts6619_start(void); + +/** + * @brief Para a tarefa de leitura e limpa os dados internos do medidor DTS6619. + */ +void meter_dts6619_stop(void); + + +#ifdef __cplusplus } +#endif -static esp_err_t ocpp_post_config_handler(httpd_req_t *req) { - ESP_LOGD(TAG, "POST /api/v1/config/ocpp"); +#endif /* METER_DTS6619_H_ */ - char buf[512]; - int len = httpd_req_recv(req, buf, sizeof(buf) - 1); - if (len <= 0) { - httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Empty body"); - return ESP_FAIL; - } - buf[len] = '\0'; +// === Fim de: components/meter_manager/driver/meter_orno/meter_ea777.h === - cJSON *json = cJSON_Parse(buf); - if (!json) { - httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON"); - return ESP_FAIL; - } - cJSON *enabled = cJSON_GetObjectItem(json, "enabled"); - if (cJSON_IsBool(enabled)) { - ocpp_set_enabled(cJSON_IsTrue(enabled)); - } +// === Início de: components/meter_manager/driver/meter_orno/meter_orno513.h === +#pragma once - cJSON *url = cJSON_GetObjectItem(json, "url"); - if (cJSON_IsString(url)) { - ocpp_set_server(url->valuestring); - } +#ifdef __cplusplus +extern "C" { +#endif - cJSON *id = cJSON_GetObjectItem(json, "chargeBoxId"); - if (cJSON_IsString(id)) { - ocpp_set_charge_id(id->valuestring); - } +#include +#include +#include "esp_err.h" - cJSON_Delete(json); - httpd_resp_sendstr(req, "OCPP config atualizada com sucesso"); - return ESP_OK; +/** + * @brief Inicializa o driver do medidor ORNO 513 (SPI, mutex, registradores). + */ +esp_err_t meter_orno513_init(void); + +/** + * @brief Inicia a tarefa de leitura de dados do medidor ORNO 513. + */ +esp_err_t meter_orno513_start(void); + +/** + * @brief Para a tarefa de leitura e limpa os dados internos do medidor ORNO 513. + */ +void meter_orno513_stop(void); + + +#ifdef __cplusplus } +#endif -void register_ocpp_handlers(httpd_handle_t server, void *ctx) { - httpd_uri_t status_uri = { - .uri = "/api/v1/ocpp", - .method = HTTP_GET, - .handler = ocpp_get_status_handler, - .user_ctx = ctx - }; - httpd_register_uri_handler(server, &status_uri); +// === Fim de: components/meter_manager/driver/meter_orno/meter_orno513.h === - httpd_uri_t get_uri = { - .uri = "/api/v1/config/ocpp", - .method = HTTP_GET, - .handler = ocpp_get_config_handler, - .user_ctx = ctx - }; - httpd_register_uri_handler(server, &get_uri); - httpd_uri_t post_uri = { - .uri = "/api/v1/config/ocpp", - .method = HTTP_POST, - .handler = ocpp_post_config_handler, - .user_ctx = ctx - }; - httpd_register_uri_handler(server, &post_uri); +// === Início de: components/meter_manager/driver/meter_orno/meter_orno516.h === +#ifndef METER_ORNO516_H_ +#define METER_ORNO516_H_ + +#include +#include +#include "esp_err.h" + +/** + * @brief Inicializa o driver do medidor ORNO 516 (SPI, mutex, registradores). + * + * @return esp_err_t Retorna ESP_OK se a inicialização for bem-sucedida, caso contrário retorna um erro. + */ +esp_err_t meter_orno516_init(void); + +/** + * @brief Inicia a tarefa de leitura de dados do medidor ORNO 516. + * + * @return esp_err_t Retorna ESP_OK se a tarefa for iniciada com sucesso, caso contrário retorna um erro. + */ +esp_err_t meter_orno516_start(void); + +/** + * @brief Para a tarefa de leitura e limpa os dados internos do medidor ORNO 516. + */ +void meter_orno516_stop(void); + + +#ifdef __cplusplus } +#endif -// === Fim de: components/rest_api/src/ocpp_api.c === +#endif /* METER_ORNO516_H_ */ + +// === Fim de: components/meter_manager/driver/meter_orno/meter_orno516.h === -// === Início de: components/rest_api/src/static_file_api.c === -#include "static_file_api.h" -#include "esp_log.h" -#include -#include -#include "esp_vfs.h" +// === Início de: components/meter_manager/driver/meter_orno/meter_orno526.h === +#pragma once -static const char *TAG = "static_file_api"; +#ifdef __cplusplus +extern "C" { +#endif -#define FILE_PATH_MAX (ESP_VFS_PATH_MAX + 128) -#define SCRATCH_BUFSIZE (10240) +#include +#include +#include "esp_err.h" -typedef struct rest_server_context { - char base_path[ESP_VFS_PATH_MAX + 1]; - char scratch[SCRATCH_BUFSIZE]; -} rest_server_context_t; +/** + * @brief Inicializa o driver do medidor ORNO 526 (SPI, mutex, registradores). + */ +esp_err_t meter_orno526_init(void); -#define CHECK_FILE_EXTENSION(filename, ext) \ - (strcasecmp(&filename[strlen(filename) - strlen(ext)], ext) == 0) +/** + * @brief Inicia a tarefa de leitura de dados do medidor ORNO 526. + */ +esp_err_t meter_orno526_start(void); -static esp_err_t set_content_type_from_file(httpd_req_t *req, const char *filepath) { - const char *type = "text/plain"; - if (CHECK_FILE_EXTENSION(filepath, ".html")) type = "text/html"; - else if (CHECK_FILE_EXTENSION(filepath, ".js")) type = "application/javascript"; - else if (CHECK_FILE_EXTENSION(filepath, ".css")) type = "text/css"; - else if (CHECK_FILE_EXTENSION(filepath, ".png")) type = "image/png"; - else if (CHECK_FILE_EXTENSION(filepath, ".ico")) type = "image/x-icon"; - else if (CHECK_FILE_EXTENSION(filepath, ".svg")) type = "image/svg+xml"; - return httpd_resp_set_type(req, type); +/** + * @brief Para a tarefa de leitura e limpa os dados internos do medidor ORNO 526. + */ +void meter_orno526_stop(void); + + +#ifdef __cplusplus } +#endif -static esp_err_t static_get_handler(httpd_req_t *req) { - char filepath[FILE_PATH_MAX]; - rest_server_context_t *ctx = (rest_server_context_t *) req->user_ctx; +// === Fim de: components/meter_manager/driver/meter_orno/meter_orno526.h === - strlcpy(filepath, ctx->base_path, sizeof(filepath)); - if (req->uri[strlen(req->uri) - 1] == '/') { - strlcat(filepath, "/index.html", sizeof(filepath)); - } else { - strlcat(filepath, req->uri, sizeof(filepath)); - } - int fd = open(filepath, O_RDONLY, 0); - if (fd == -1) { - // fallback para /index.html (SPA) - ESP_LOGW(TAG, "Arquivo não encontrado: %s. Tentando index.html", filepath); - strlcpy(filepath, ctx->base_path, sizeof(filepath)); - strlcat(filepath, "/index.html", sizeof(filepath)); - fd = open(filepath, O_RDONLY, 0); - if (fd == -1) { - httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "Arquivo não encontrado"); - return ESP_FAIL; - } - } +// === Início de: components/meter_manager/driver/meter_orno/meter_dds661.h === +#pragma once - set_content_type_from_file(req, filepath); +#ifdef __cplusplus +extern "C" { +#endif - char *chunk = ctx->scratch; - ssize_t read_bytes; - do { - read_bytes = read(fd, chunk, SCRATCH_BUFSIZE); - if (read_bytes == -1) { - ESP_LOGE(TAG, "Erro lendo arquivo: %s", filepath); - close(fd); - httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Erro ao ler arquivo"); - return ESP_FAIL; - } else if (read_bytes > 0) { - if (httpd_resp_send_chunk(req, chunk, read_bytes) != ESP_OK) { - close(fd); - httpd_resp_sendstr_chunk(req, NULL); - httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Erro ao enviar arquivo"); - return ESP_FAIL; - } - } - } while (read_bytes > 0); +#include +#include +#include "esp_err.h" - close(fd); - httpd_resp_send_chunk(req, NULL, 0); - return ESP_OK; +/** + * @brief Inicializa o driver do medidor DDS 661 (SPI, mutex, registradores). + */ +esp_err_t meter_dds661_init(void); + +/** + * @brief Inicia a tarefa de leitura de dados do medidor DDS 661. + */ +esp_err_t meter_dds661_start(void); + +/** + * @brief Para a tarefa de leitura e limpa os dados internos do medidor DDS 661. + */ +void meter_dds661_stop(void); + + +#ifdef __cplusplus } +#endif -void register_static_file_handlers(httpd_handle_t server, void *ctx) { - httpd_uri_t uri = { - .uri = "/*", - .method = HTTP_GET, - .handler = static_get_handler, - .user_ctx = ctx - }; - httpd_register_uri_handler(server, &uri); +// === Fim de: components/meter_manager/driver/meter_orno/meter_dds661.h === + + +// === Início de: components/meter_manager/driver/meter_orno/meter_dts6619.h === +#ifndef METER_EA777_H_ +#define METER_EA777_H_ + +#include +#include +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Inicializa o driver do medidor EA777 (UART RS485, Modbus, registradores). + * + * @return esp_err_t Retorna ESP_OK se a inicialização for bem-sucedida, caso contrário retorna um erro. + */ +esp_err_t meter_ea777_init(void); + +/** + * @brief Inicia a tarefa de leitura de dados do medidor EA777. + * + * @return esp_err_t Retorna ESP_OK se a tarefa for iniciada com sucesso, caso contrário retorna um erro. + */ +esp_err_t meter_ea777_start(void); + +/** + * @brief Para a tarefa de leitura e limpa os dados internos do medidor EA777. + */ +void meter_ea777_stop(void); + +#ifdef __cplusplus } +#endif -// === Fim de: components/rest_api/src/static_file_api.c === +#endif /* METER_EA777_H_ */ + +// === Fim de: components/meter_manager/driver/meter_orno/meter_dts6619.h === -// === Início de: components/rest_api/src/evse_link_config_api.c === -#include "evse_link.h" -#include "evse_link_config_api.h" // new header for these handlers -#include "esp_log.h" -#include "cJSON.h" +// === Início de: components/meter_manager/driver/meter_zigbee/meter_zigbee.h === +#pragma once -static const char *TAG = "link_config_api"; +#ifdef __cplusplus +extern "C" { +#endif -// GET /api/v1/config/link -static esp_err_t link_config_get_handler(httpd_req_t *req) { - bool enabled = evse_link_is_enabled(); - uint8_t mode = evse_link_get_mode(); // 0=MASTER,1=SLAVE - uint8_t self_id = evse_link_get_self_id(); +#include +#include +#include "esp_err.h" - ESP_LOGI(TAG, "GET link config: enabled=%d mode=%u id=%u", - enabled, mode, self_id); +/** + * @brief Inicializa o driver do medidor Zigbee (UART, mutex, etc.). + * + * @return ESP_OK se a inicialização for bem-sucedida, erro caso contrário. + */ +esp_err_t meter_zigbee_init(void); - httpd_resp_set_type(req, "application/json"); - cJSON *root = cJSON_CreateObject(); - cJSON_AddBoolToObject (root, "linkEnabled", enabled); - cJSON_AddStringToObject(root, "linkMode", - mode == EVSE_LINK_MODE_MASTER ? "MASTER" : "SLAVE"); - cJSON_AddNumberToObject(root, "linkSelfId", self_id); +/** + * @brief Inicia a tarefa de leitura dos dados do medidor Zigbee. + * + * @return ESP_OK se a tarefa for iniciada com sucesso, erro caso contrário. + */ +esp_err_t meter_zigbee_start(void); - char *s = cJSON_Print(root); - httpd_resp_sendstr(req, s); - ESP_LOGI(TAG, " payload: %s", s); - free(s); - cJSON_Delete(root); - return ESP_OK; +/** + * @brief Interrompe a tarefa e limpa recursos (UART, mutex, etc.). + */ +void meter_zigbee_stop(void); + + +#ifdef __cplusplus } +#endif -// POST /api/v1/config/link -static esp_err_t link_config_post_handler(httpd_req_t *req) { - char buf[256]; - int len = httpd_req_recv(req, buf, sizeof(buf)-1); - if (len <= 0) { - httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Empty body"); - return ESP_FAIL; - } - buf[len] = '\0'; - ESP_LOGI(TAG, "POST link config: %s", buf); - - cJSON *json = cJSON_Parse(buf); - if (!json) { - httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON"); - return ESP_FAIL; - } - - // linkEnabled - cJSON *j_en = cJSON_GetObjectItem(json, "linkEnabled"); - if (j_en && cJSON_IsBool(j_en)) { - evse_link_set_enabled(cJSON_IsTrue(j_en)); - ESP_LOGI(TAG, " set enabled = %d", cJSON_IsTrue(j_en)); - } - - // linkMode - cJSON *j_md = cJSON_GetObjectItem(json, "linkMode"); - if (j_md && cJSON_IsString(j_md)) { - const char *m = j_md->valuestring; - if (strcmp(m, "MASTER") == 0) { - evse_link_set_mode(EVSE_LINK_MODE_MASTER); - } else if (strcmp(m, "SLAVE") == 0) { - evse_link_set_mode(EVSE_LINK_MODE_SLAVE); - } else { - cJSON_Delete(json); - httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, - "Invalid linkMode (must be MASTER or SLAVE)"); - return ESP_FAIL; - } - ESP_LOGI(TAG, " set mode = %s", m); - } - - // linkSelfId - cJSON *j_id = cJSON_GetObjectItem(json, "linkSelfId"); - if (j_id && cJSON_IsNumber(j_id)) { - int id = j_id->valueint; - if (id < 0 || id > 254) { - cJSON_Delete(json); - httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, - "Invalid linkSelfId (0–254)"); - return ESP_FAIL; - } - evse_link_set_self_id((uint8_t)id); - ESP_LOGI(TAG, " set self_id = %d", id); - } - - cJSON_Delete(json); - httpd_resp_sendstr(req, "Link settings updated"); - return ESP_OK; -} - -void register_link_config_handlers(httpd_handle_t server, void *ctx) { - httpd_uri_t get = { - .uri = "/api/v1/config/link", - .method = HTTP_GET, - .handler = link_config_get_handler, - .user_ctx = ctx - }; - httpd_register_uri_handler(server, &get); - - httpd_uri_t post = { - .uri = "/api/v1/config/link", - .method = HTTP_POST, - .handler = link_config_post_handler, - .user_ctx = ctx - }; - httpd_register_uri_handler(server, &post); -} - -// === Fim de: components/rest_api/src/evse_link_config_api.c === +// === Fim de: components/meter_manager/driver/meter_zigbee/meter_zigbee.h === -// === Início de: components/rest_api/src/scheduler_settings_api.c === -#include "scheduler_settings_api.h" -#include "scheduler.h" -#include "scheduler_types.h" +// === Início de: components/meter_manager/include/meter_manager.h === +#ifndef METER_MANAGER_H +#define METER_MANAGER_H -#include "esp_log.h" -#include "esp_http_server.h" -#include "cJSON.h" +#include "esp_err.h" +#include -#include -#include -#include // sscanf, snprintf +/** + * @brief Supported meter types for EVSE and Grid. + */ +typedef enum { + METER_TYPE_NONE, // No meter + METER_TYPE_ADE7758, // ADE7758 meter + METER_TYPE_ORNO513, // ORNO-513 + METER_TYPE_ORNO516, // ORNO-516 + METER_TYPE_ORNO526, // ORNO-516 + METER_TYPE_DDS661, // DDS-661 + METER_TYPE_DTS6619, // dts6619 + METER_TYPE_MONO_ZIGBEE, // Zigbee single-phase + METER_TYPE_TRIF_ZIGBEE, // Zigbee three-phase + METER_TYPE_EA777 // EA777 +} meter_type_t; -static const char *TAG = "scheduler_api"; - -/* ========================= - * Helpers HH:MM <-> minutos - * ========================= */ - -static bool parse_hhmm(const char *s, uint16_t *out_min) -{ - if (!s || !out_min) - return false; - - // formato esperado: "HH:MM" - int h = 0, m = 0; - if (sscanf(s, "%d:%d", &h, &m) != 2) - { - return false; - } - if (h < 0 || h > 23 || m < 0 || m > 59) - { - return false; - } - *out_min = (uint16_t)(h * 60 + m); - return true; -} - -static void format_hhmm(uint16_t minutes, char *buf, size_t buf_sz) -{ - if (!buf || buf_sz < 6) - return; - minutes %= (24 * 60); - int h = minutes / 60; - int m = minutes % 60; - snprintf(buf, buf_sz, "%02d:%02d", h, m); -} - -/* ========================= - * GET /api/v1/config/scheduler - * ========================= */ -static esp_err_t scheduler_config_get_handler(httpd_req_t *req) -{ - ESP_LOGI(TAG, "GET /api/v1/config/scheduler"); - - httpd_resp_set_type(req, "application/json"); - - sched_config_t cfg = scheduler_get_config(); - bool allowed_now = scheduler_is_allowed_now(); - - cJSON *root = cJSON_CreateObject(); - if (!root) - { - httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "cJSON alloc failed"); - return ESP_FAIL; - } - - cJSON_AddBoolToObject(root, "enabled", cfg.enabled); - cJSON_AddStringToObject(root, "mode", sched_mode_to_str(cfg.mode)); - - char buf[8]; - format_hhmm(cfg.start_min, buf, sizeof(buf)); - cJSON_AddStringToObject(root, "startTime", buf); - format_hhmm(cfg.end_min, buf, sizeof(buf)); - cJSON_AddStringToObject(root, "endTime", buf); - - cJSON_AddBoolToObject(root, "allowedNow", allowed_now); - - char *json_str = cJSON_PrintUnformatted(root); - if (!json_str) - { - cJSON_Delete(root); - httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "cJSON print failed"); - return ESP_FAIL; - } - - httpd_resp_sendstr(req, json_str); - - free(json_str); - cJSON_Delete(root); - return ESP_OK; -} - -/* ========================= - * POST /api/v1/config/scheduler - * ========================= */ -static esp_err_t scheduler_config_post_handler(httpd_req_t *req) -{ - ESP_LOGI(TAG, "POST /api/v1/config/scheduler"); - - // NOTA: para payloads pequenos 512 bytes chega; se quiseres robustez total, - // usa req->content_len e faz um loop com httpd_req_recv. - char buf[512]; - int len = httpd_req_recv(req, buf, sizeof(buf) - 1); - if (len <= 0) - { - ESP_LOGE(TAG, "Empty body / recv error"); - httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Empty body"); - return ESP_FAIL; - } - buf[len] = '\0'; - ESP_LOGI(TAG, "Body: %s", buf); - - cJSON *json = cJSON_Parse(buf); - if (!json) - { - ESP_LOGE(TAG, "Invalid JSON"); - httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON"); - return ESP_FAIL; - } - - // Começa a partir da config atual - sched_config_t cfg = scheduler_get_config(); - - // enabled - cJSON *j_enabled = cJSON_GetObjectItem(json, "enabled"); - if (cJSON_IsBool(j_enabled)) - { - cfg.enabled = cJSON_IsTrue(j_enabled); - ESP_LOGI(TAG, " enabled = %d", cfg.enabled); - } - - // mode - cJSON *j_mode = cJSON_GetObjectItem(json, "mode"); - if (cJSON_IsString(j_mode) && j_mode->valuestring) - { - sched_mode_t m; - if (!sched_mode_from_str(j_mode->valuestring, &m)) - { - ESP_LOGW(TAG, "Invalid mode: %s", j_mode->valuestring); - cJSON_Delete(json); - httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, - "Invalid mode (use: disabled|simple|weekly)"); - return ESP_FAIL; - } - cfg.mode = m; - ESP_LOGI(TAG, " mode = %s", sched_mode_to_str(cfg.mode)); - } - - // startTime (string "HH:MM") - cJSON *j_start = cJSON_GetObjectItem(json, "startTime"); - if (cJSON_IsString(j_start) && j_start->valuestring) - { - uint16_t minutes = 0; - if (!parse_hhmm(j_start->valuestring, &minutes)) - { - ESP_LOGW(TAG, "Invalid startTime: %s", j_start->valuestring); - cJSON_Delete(json); - httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, - "Invalid startTime (use HH:MM)"); - return ESP_FAIL; - } - cfg.start_min = minutes; - ESP_LOGI(TAG, " start_min = %u", (unsigned)cfg.start_min); - } - - // endTime (string "HH:MM") - cJSON *j_end = cJSON_GetObjectItem(json, "endTime"); - if (cJSON_IsString(j_end) && j_end->valuestring) - { - uint16_t minutes = 0; - if (!parse_hhmm(j_end->valuestring, &minutes)) - { - ESP_LOGW(TAG, "Invalid endTime: %s", j_end->valuestring); - cJSON_Delete(json); - httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, - "Invalid endTime (use HH:MM)"); - return ESP_FAIL; - } - cfg.end_min = minutes; - ESP_LOGI(TAG, " end_min = %u", (unsigned)cfg.end_min); - } - - // (Opcional) validações extra: - // exemplo: impedir janela vazia quando ativo - /* - if (cfg.enabled && cfg.mode != SCHED_MODE_DISABLED && - cfg.start_min == cfg.end_min) { - cJSON_Delete(json); - httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, - "startTime and endTime cannot be equal"); - return ESP_FAIL; - } - */ - - // Aplica config no módulo scheduler (ele trata de NVS + eventos) - scheduler_set_config(&cfg); - - cJSON_Delete(json); - - httpd_resp_sendstr(req, "Scheduler config atualizada com sucesso"); - return ESP_OK; -} - -/* ========================= - * Registo dos handlers - * ========================= */ -void register_scheduler_settings_handlers(httpd_handle_t server, void *ctx) -{ - httpd_uri_t get_uri = { - .uri = "/api/v1/config/scheduler", - .method = HTTP_GET, - .handler = scheduler_config_get_handler, - .user_ctx = ctx}; - httpd_register_uri_handler(server, &get_uri); - - httpd_uri_t post_uri = { - .uri = "/api/v1/config/scheduler", - .method = HTTP_POST, - .handler = scheduler_config_post_handler, - .user_ctx = ctx}; - httpd_register_uri_handler(server, &post_uri); - - ESP_LOGI(TAG, "Scheduler REST handlers registered"); -} - -// === Fim de: components/rest_api/src/scheduler_settings_api.c === +/** + * @brief Initializes the meter manager system. + * + * Registers network event handlers and initializes both EVSE and GRID meters. + */ +esp_err_t meter_manager_init(void); -// === Início de: components/rest_api/src/meters_settings_api.c === -#include "meters_settings_api.h" -#include "meter_manager.h" // Atualizado para usar o novo manager -#include "meter_events.h" +/** + * @brief Starts all configured meters (EVSE and GRID). + * + * @return esp_err_t ESP_OK on success, or an error code from one of the start calls. + */ +esp_err_t meter_manager_start(void); + + +/** + * @brief Stops all meters and unregisters network event handlers. + */ +esp_err_t meter_manager_stop(void); + +/** + * @brief EVSE Meter Management + */ + +/** + * @brief Initializes the EVSE meter based on configured type. + */ +esp_err_t meter_manager_evse_init(void); + +/** + * @brief Starts the EVSE meter. + */ +esp_err_t meter_manager_evse_start(void); + +/** + * @brief Stops the EVSE meter. + */ +esp_err_t meter_manager_evse_stop(void); + +/** + * @brief Returns true if an EVSE meter is configured (not NONE). + */ +bool meter_manager_evse_is_enabled(void); + +/** + * @brief Sets the EVSE meter type and saves it to NVS. + */ +esp_err_t meter_manager_evse_set_model(meter_type_t meter_type); + +/** + * @brief Gets the current EVSE meter type. + */ +meter_type_t meter_manager_evse_get_model(void); + +/** + * @brief Grid Meter Management + */ + +/** + * @brief Initializes the Grid meter based on configured type. + */ +esp_err_t meter_manager_grid_init(void); + +/** + * @brief Starts the Grid meter. + */ +esp_err_t meter_manager_grid_start(void); + +/** + * @brief Stops the Grid meter. + */ +esp_err_t meter_manager_grid_stop(void); + +/** + * @brief Sets the Grid meter type and saves it to NVS. + */ +esp_err_t meter_manager_grid_set_model(meter_type_t meter_type); + +/** + * @brief Gets the current Grid meter type. + */ +meter_type_t meter_manager_grid_get_model(void); + +/** + * @brief Utility functions + */ + +/** + * @brief Converts a meter_type_t to a human-readable string. + */ +const char* meter_type_to_str(meter_type_t type); + +/** + * @brief Converts a string to a meter_type_t. + */ +meter_type_t string_to_meter_type(const char *str); + +#endif // METER_MANAGER_H + +// === Fim de: components/meter_manager/include/meter_manager.h === + + +// === Início de: components/meter_manager/include/meter_events.h === +#ifndef METER_EVENTS_H +#define METER_EVENTS_H + #include "esp_event.h" -#include "esp_log.h" -#include "cJSON.h" -#include "freertos/semphr.h" +#include "meter_manager.h" // Para meter_type_t + +#ifdef __cplusplus +extern "C" { +#endif + +// Base de eventos dos medidores +ESP_EVENT_DECLARE_BASE(METER_EVENT); + +// IDs de eventos emitidos por medidores +typedef enum { + METER_EVENT_DATA_READY = 0, + METER_EVENT_ERROR, + METER_EVENT_STARTED, + METER_EVENT_STOPPED +} meter_event_id_t; + +// Estrutura de dados enviados com METER_EVENT_DATA_READY +typedef struct { + const char *source; // "GRID" ou "EVSE" + float vrms[3]; // Tensão por fase + float irms[3]; // Corrente por fase + int watt[3]; // Potência ativa por fase + float frequency; // Frequência da rede (Hz) + float power_factor; // Fator de potência + float total_energy; // Energia acumulada (kWh) +} meter_event_data_t; + + +#ifdef __cplusplus +} +#endif + +#endif // METER_EVENTS_H + +// === Fim de: components/meter_manager/include/meter_events.h === + + +// === Início de: components/loadbalancer/include/loadbalancer_events.h === +#pragma once +#include "esp_event.h" +#include +#include #include "esp_timer.h" -#include -static const char *TAG = "meters_settings_api"; +ESP_EVENT_DECLARE_BASE(LOADBALANCER_EVENTS); + +typedef enum { + LOADBALANCER_EVENT_INIT, + LOADBALANCER_EVENT_STATE_CHANGED, + LOADBALANCER_EVENT_GLOBAL_CURRENT_LIMIT, + LOADBALANCER_EVENT_MASTER_CURRENT_LIMIT, + LOADBALANCER_EVENT_SLAVE_CURRENT_LIMIT, + LOADBALANCER_EVENT_SLAVE_STATUS +} loadbalancer_event_id_t; + +typedef struct { + bool enabled; + int64_t timestamp_us; +} loadbalancer_state_event_t; + +// (opcional) +typedef struct { + float limit; + int64_t timestamp_us; +} loadbalancer_global_limit_event_t; + +typedef struct { + uint8_t slave_id; + uint16_t max_current; + int64_t timestamp_us; +} loadbalancer_master_limit_event_t; + +typedef struct { + uint8_t slave_id; + uint16_t max_current; + int64_t timestamp_us; +} loadbalancer_slave_limit_event_t; + +typedef struct { + uint8_t slave_id; // ID do slave que reportou + bool charging; // Status de carregamento + float hw_max_current; // Limite máximo de corrente do hardware informado + float runtime_current; // Corrente atual de carregamento (A) + int64_t timestamp_us; // Momento em que o status foi coletado +} loadbalancer_slave_status_event_t; +// === Fim de: components/loadbalancer/include/loadbalancer_events.h === + + +// === Início de: components/loadbalancer/include/loadbalancer.h === +#ifndef LOADBALANCER_H_ +#define LOADBALANCER_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include "esp_err.h" + + +/** + * @brief Inicializa o módulo de load balancer + */ +void loadbalancer_init(void); + +/** + * @brief Task contínua do algoritmo de balanceamento + */ +void loadbalancer_task(void *param); + +/** + * @brief Ativa ou desativa o load balancing + */ +void loadbalancer_set_enabled(bool value); + +/** + * @brief Verifica se o load balancing está ativo + */ +bool loadbalancer_is_enabled(void); + +/** + * @brief Define a corrente máxima do grid + */ +esp_err_t load_balancing_set_max_grid_current(uint8_t max_grid_current); + +/** + * @brief Obtém a corrente máxima do grid + */ +uint8_t load_balancing_get_max_grid_current(void); + +#ifdef __cplusplus +} +#endif + +#endif /* LOADBALANCER_H_ */ + +// === Fim de: components/loadbalancer/include/loadbalancer.h === + + +// === Início de: components/loadbalancer/include/input_filter.h === +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + float alpha; ///< Fator de suavização (0.0 a 1.0) + float value; ///< Último valor filtrado + int initialized; ///< Flag de inicialização +} input_filter_t; + +/** + * @brief Inicializa o filtro com o fator alpha desejado. + * @param filter Ponteiro para a estrutura do filtro + * @param alpha Valor entre 0.0 (mais lento) e 1.0 (sem filtro) + */ +void input_filter_init(input_filter_t *filter, float alpha); + +/** + * @brief Atualiza o valor filtrado com uma nova entrada. + * @param filter Ponteiro para o filtro + * @param input Valor bruto + * @return Valor suavizado + */ +float input_filter_update(input_filter_t *filter, float input); + +#ifdef __cplusplus +} +#endif + +// === Fim de: components/loadbalancer/include/input_filter.h === + + +// === Início de: components/auth/include/auth.h === +#pragma once +#include +#include "auth_types.h" // enum + MAX LEN para API + +#ifdef __cplusplus +extern "C" { +#endif + +/* Evento auxiliar legado/útil (resultado local) */ +typedef struct { + char tag[AUTH_TAG_MAX_LEN]; + bool authorized; +} auth_event_t; + +void auth_init(void); +void auth_set_mode(auth_mode_t mode); +auth_mode_t auth_get_mode(void); + +bool auth_add_tag(const char *tag); +bool auth_remove_tag(const char *tag); +bool auth_tag_exists(const char *tag); +void auth_list_tags(void); + +void auth_process_tag(const char *tag); +void auth_wait_for_tag_registration(void); + +int auth_get_tag_count(void); +const char *auth_get_tag_by_index(int index); + +#ifdef __cplusplus +} +#endif + +// === Fim de: components/auth/include/auth.h === + + +// === Início de: components/auth/include/auth_events.h === +#pragma once +#include "esp_event.h" +#include "auth_types.h" // só tipos comuns; evita incluir auth.h + +ESP_EVENT_DECLARE_BASE(AUTH_EVENTS); + +/* IDs de eventos */ +typedef enum { + AUTH_EVENT_TAG_PROCESSED = 0, // resultado LOCAL -> auth_tag_event_data_t + AUTH_EVENT_TAG_VERIFY, // pedir validação OCPP -> auth_tag_verify_event_t + AUTH_EVENT_TAG_SAVED, // registada (modo registo) -> auth_tag_event_data_t + AUTH_EVENT_MODE_CHANGED, // modo alterado -> auth_mode_event_data_t + AUTH_EVENT_INIT, // estado inicial -> auth_mode_event_data_t +} auth_event_id_t; + +/* Payloads */ +typedef struct { + char tag[AUTH_TAG_MAX_LEN]; + bool authorized; +} auth_tag_event_data_t; + +typedef struct { + char tag[AUTH_TAG_MAX_LEN]; + uint32_t req_id; // opcional p/ correlacionar +} auth_tag_verify_event_t; + +typedef struct { + auth_mode_t mode; +} auth_mode_event_data_t; + +// === Fim de: components/auth/include/auth_events.h === + + +// === Início de: components/auth/include/wiegand.h === +/* + * Copyright (c) 2021 Ruslan V. Uss + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file wiegand.h + * @defgroup wiegand wiegand + * @{ + * + * ESP-IDF Wiegand protocol receiver + * + * Copyright (c) 2021 Ruslan V. Uss + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __WIEGAND_H__ +#define __WIEGAND_H__ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct wiegand_reader wiegand_reader_t; + +typedef void (*wiegand_callback_t)(wiegand_reader_t *reader); + +/** + * Bit and byte order of data + */ +typedef enum { + WIEGAND_MSB_FIRST = 0, + WIEGAND_LSB_FIRST +} wiegand_order_t; + +/** + * Wiegand reader descriptor + */ +struct wiegand_reader +{ + gpio_num_t gpio_d0, gpio_d1; + wiegand_callback_t callback; + wiegand_order_t bit_order; + wiegand_order_t byte_order; + + uint8_t *buf; + size_t size; + size_t bits; + esp_timer_handle_t timer; + bool start_parity; + bool enabled; +}; + +/** + * @brief Create and initialize reader instance. + * + * @param reader Reader descriptor + * @param gpio_d0 GPIO pin for D0 + * @param gpio_d1 GPIO pin for D0 + * @param internal_pullups Enable internal pull-up resistors for D0 and D1 GPIO + * @param buf_size Reader buffer size in bytes, must be large enough to + * contain entire Wiegand key + * @param callback Callback function for processing received codes + * @param bit_order Bit order of data + * @param byte_order Byte order of data + * @return `ESP_OK` on success + */ +esp_err_t wiegand_reader_init(wiegand_reader_t *reader, gpio_num_t gpio_d0, gpio_num_t gpio_d1, + bool internal_pullups, size_t buf_size, wiegand_callback_t callback, wiegand_order_t bit_order, + wiegand_order_t byte_order); + +/** + * @brief Disable reader + * + * While reader is disabled, it will not receive new data + * + * @param reader Reader descriptor + * @return `ESP_OK` on success + */ +esp_err_t wiegand_reader_disable(wiegand_reader_t *reader); + +/** + * @brief Enable reader + * + * @param reader Reader descriptor + * @return `ESP_OK` on success + */ +esp_err_t wiegand_reader_enable(wiegand_reader_t *reader); + +/** + * @brief Delete reader instance. + * + * @param reader Reader descriptor + * @return `ESP_OK` on success + */ +esp_err_t wiegand_reader_done(wiegand_reader_t *reader); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __WIEGAND_H__ */ + +// === Fim de: components/auth/include/wiegand.h === + + +// === Início de: components/auth/include/auth_types.h === +#pragma once +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Tamanho máx. da tag (inclui NUL) */ +#define AUTH_TAG_MAX_LEN 30 + +/* Modos de autorização */ +typedef enum { + AUTH_MODE_OPEN = 0, // Sem autenticação + AUTH_MODE_LOCAL_RFID, // Lista local (NVS) + AUTH_MODE_OCPP_RFID // Validação via OCPP/CSMS +} auth_mode_t; + +/* Converte enum -> "open"|"local"|"ocpp" (nunca NULL) */ +const char *auth_mode_to_str(auth_mode_t mode); + +/* Converte "open"|"local"|"ocpp" (case-insensitive) -> enum */ +bool auth_mode_from_str(const char *s, auth_mode_t *out); + +#ifdef __cplusplus +} +#endif + +// === Fim de: components/auth/include/auth_types.h === + + +// === Início de: components/auth/include/wiegand_reader.h === +#ifndef WIEGAND_READER_H +#define WIEGAND_READER_H + +#ifdef __cplusplus +extern "C" { +#endif + +void initWiegand(void); + +#ifdef __cplusplus +} +#endif + +#endif // WIEGAND_READER_H + +// === Fim de: components/auth/include/wiegand_reader.h === + + +// === Início de: components/evse/include/evse_pilot.h === +#ifndef PILOT_H_ +#define PILOT_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +/** + * @brief Níveis categóricos de tensão no sinal CP (Control Pilot) + */ +typedef enum +{ + PILOT_VOLTAGE_12, ///< Estado A: +12V + PILOT_VOLTAGE_9, ///< Estado B: +9V + PILOT_VOLTAGE_6, ///< Estado C: +6V + PILOT_VOLTAGE_3, ///< Estado D: +3V + PILOT_VOLTAGE_1 ///< Estado E/F: abaixo de 3V +} pilot_voltage_t; + +/** + * @brief Inicializa o driver do sinal Pilot + */ +void pilot_init(void); + +/** + * @brief Define o nível do Pilot: +12V ou -12V + * + * @param level true = +12V, false = -12V + */ +void pilot_set_level(bool level); + +/** + * @brief Ativa o PWM do Pilot com corrente limitada + * + * @param amps Corrente em ampères (ex: 16 = 16A) + */ +void pilot_set_amps(uint16_t amps); + +/** + * @brief Mede o nível de tensão do Pilot e detecta -12V + * + * @param up_voltage Valor categórico da tensão positiva + * @param down_voltage_n12 true se o nível negativo atingir -12V + */ +void pilot_measure(pilot_voltage_t *up_voltage, bool *down_voltage_n12); + +/** + * @brief Retorna o estado lógico atual do Pilot (nível alto = +12V) + * + * @return true se nível atual for +12V, false se for -12V + */ +bool pilot_get_state(void); + +/** + * @brief Cache interno opcional dos níveis de tensão reais do Pilot + */ +typedef struct { + uint16_t high_mv; ///< Pico positivo medido (mV) + uint16_t low_mv; ///< Pico negativo medido (mV) +} pilot_voltage_cache_t; + +#ifdef __cplusplus +} +#endif + +#endif /* PILOT_H_ */ + +// === Fim de: components/evse/include/evse_pilot.h === + + +// === Início de: components/evse/include/evse_manager.h === +#ifndef EVSE_MANAGER_H +#define EVSE_MANAGER_H + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include + +/** + * @brief Inicializa os módulos internos do EVSE (hardware, estado, erros, etc.) + * e inicia a tarefa de supervisão periódica (tick). + */ +void evse_manager_init(void); + +/** + * @brief Executa uma iteração do ciclo de controle do EVSE. + * + * Esta função é chamada automaticamente pela task periódica, + * mas pode ser chamada manualmente em testes. + */ +void evse_manager_tick(void); + +#ifdef __cplusplus +} +#endif + + +#endif // EVSE_MANAGER_H + +// === Fim de: components/evse/include/evse_manager.h === + + +// === Início de: components/evse/include/evse_fsm.h === +#ifndef EVSE_FSM_H +#define EVSE_FSM_H + +#include +#include +#include "evse_api.h" +#include "evse_pilot.h" +#include "freertos/FreeRTOS.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Reinicia a máquina de estados do EVSE para o estado inicial (A). + */ +void evse_fsm_reset(void); + +/** + * @brief Processa uma leitura do sinal de piloto e atualiza a máquina de estados do EVSE. + * + * Esta função deve ser chamada periodicamente pelo núcleo de controle para + * avaliar mudanças no estado do conector, disponibilidade do carregador e + * autorização do usuário. + * + * @param pilot_voltage Leitura atual da tensão do sinal piloto. + * @param authorized Indica se o carregamento foi autorizado. + * @param available Indica se o carregador está disponível (ex: sem falhas). + * @param enabled Indica se o carregador está habilitado via software. + */ +void evse_fsm_process(pilot_voltage_t pilot_voltage, bool authorized, bool available, bool enabled); + +#ifdef __cplusplus +} +#endif + +#endif // EVSE_FSM_H + +// === Fim de: components/evse/include/evse_fsm.h === + + +// === Início de: components/evse/include/evse_hardware.h === +#ifndef EVSE_HARDWARE_H +#define EVSE_HARDWARE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +/** + * @brief Inicializa todos os periféricos de hardware do EVSE (pilot, relé, trava, etc.) + */ +void evse_hardware_init(void); + +/** + * @brief Executa atualizações periódicas no hardware (tick) + */ +void evse_hardware_tick(void); + +/** + * @brief Verifica se o sinal piloto está em nível alto (12V) + */ +bool evse_hardware_is_pilot_high(void); + +/** + * @brief Verifica se o veículo está fisicamente conectado via Proximity + */ +bool evse_hardware_is_vehicle_connected(void); + +/** + * @brief Verifica se há consumo de energia (corrente detectada) + */ +bool evse_hardware_is_energy_detected(void); + +/** + * @brief Liga o relé de fornecimento de energia + */ +void evse_hardware_relay_on(void); + +/** + * @brief Desliga o relé de fornecimento de energia + */ +void evse_hardware_relay_off(void); + +/** + * @brief Consulta o estado atual do relé + * @return true se ligado, false se desligado + */ +bool evse_hardware_relay_status(void); + +/** + * @brief Aciona a trava física do conector + */ +void evse_hardware_lock(void); + +/** + * @brief Libera a trava física do conector + */ +void evse_hardware_unlock(void); + +/** + * @brief Verifica se o conector está travado + */ +bool evse_hardware_is_locked(void); + +#ifdef __cplusplus +} +#endif + +#endif // EVSE_HARDWARE_H + +// === Fim de: components/evse/include/evse_hardware.h === + + +// === Início de: components/evse/include/evse_config.h === +#ifndef EVSE_CONFIG_H +#define EVSE_CONFIG_H + +#include +#include +#include "esp_err.h" +#include "freertos/FreeRTOS.h" +#include "evse_events.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// ======================== +// Limites Globais (Defines) +// ======================== + +// Corrente máxima de carregamento (configurável pelo usuário) +#define MIN_CHARGING_CURRENT_LIMIT 6 // A +#define MAX_CHARGING_CURRENT_LIMIT 32 // A + +// Corrente via cabo (proximity) — se configurável +#define MIN_CABLE_CURRENT_LIMIT 6 // A +#define MAX_CABLE_CURRENT_LIMIT 63 // A + +// ======================== +// Funções de Configuração +// ======================== + +// Inicialização +esp_err_t evse_config_init(void); +void evse_check_defaults(void); + +// Corrente de carregamento +uint8_t evse_get_max_charging_current(void); +esp_err_t evse_set_max_charging_current(uint8_t value); + +uint16_t evse_get_charging_current(void); +esp_err_t evse_set_charging_current(uint16_t value); + +uint16_t evse_get_default_charging_current(void); +esp_err_t evse_set_default_charging_current(uint16_t value); + +// Configuração de socket outlet +bool evse_get_socket_outlet(void); +esp_err_t evse_set_socket_outlet(bool socket_outlet); + +void evse_set_runtime_charging_current(uint16_t value); +uint16_t evse_get_runtime_charging_current(void); + + +// RCM +bool evse_is_rcm(void); +esp_err_t evse_set_rcm(bool rcm); + +// Temperatura +uint8_t evse_get_temp_threshold(void); +esp_err_t evse_set_temp_threshold(uint8_t threshold); + +// Disponibilidade +bool evse_config_is_available(void); +void evse_config_set_available(bool available); + +// Ativação/desativação do EVSE +bool evse_config_is_enabled(void); +void evse_config_set_enabled(bool enabled); + +#ifdef __cplusplus +} +#endif + +#endif // EVSE_CONFIG_H + +// === Fim de: components/evse/include/evse_config.h === + + +// === Início de: components/evse/include/evse_state.h === +#ifndef EVSE_STATE_H +#define EVSE_STATE_H + +#include +#include "freertos/FreeRTOS.h" +#include "evse_events.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// ============================ +// EVSE Pilot Signal States +// ============================ + +typedef enum { + EVSE_STATE_A, // EV Not Connected (12V) + EVSE_STATE_B1, // EV Connected (9V, Not Authorized) + EVSE_STATE_B2, // EV Connected (9V, Authorized and Ready) + EVSE_STATE_C1, // Charging Requested (6V, Relay Off) + EVSE_STATE_C2, // Charging Active (6V, Relay On) + EVSE_STATE_D1, // Ventilation Required (3V, Relay Off) + EVSE_STATE_D2, // Ventilation Active (3V, Relay On) + EVSE_STATE_E, // Error: Pilot Short to Ground (0V) + EVSE_STATE_F // Fault: No Pilot or EVSE Unavailable +} evse_state_t; + +// ============================ +// Initialization +// ============================ + +/** + * @brief Initializes the EVSE state machine and default state. + */ +void evse_state_init(void); + +/** + * @brief Periodic tick for state handling (optional hook). + */ +void evse_state_tick(void); + +// ============================ +// State Access & Control +// ============================ + +/** + * @brief Returns the current EVSE state. + */ +evse_state_t evse_get_state(void); + +/** + * @brief Sets the current EVSE state and emits a change event if needed. + */ +void evse_set_state(evse_state_t state); + +/** + * @brief Converts the state enum into a human-readable string. + */ +const char* evse_state_to_str(evse_state_t state); + +// ============================ +// State Evaluation Helpers +// ============================ + +/** + * @brief True if EV is in an active session (B2, C1, C2). + */ +bool evse_state_is_session(evse_state_t state); + +/** + * @brief True if EV is actively charging (C1, C2). + */ +bool evse_state_is_charging(evse_state_t state); + +/** + * @brief True if EV is physically plugged in (B1 and beyond). + */ +bool evse_state_is_plugged(evse_state_t state); + +// ============================ +// Authorization Control +// ============================ + +/** + * @brief Sets whether the EV is authorized to charge. + */ +void evse_state_set_authorized(bool authorized); + +/** + * @brief Gets whether the EV is currently authorized. + */ +bool evse_state_get_authorized(void); + +#ifdef __cplusplus +} +#endif + +#endif // EVSE_STATE_H + +// === Fim de: components/evse/include/evse_state.h === + + +// === Início de: components/evse/include/evse_error.h === +// === Início de: components/evse/include/evse_error.h === +#ifndef EVSE_ERROR_H +#define EVSE_ERROR_H + +#include +#include +#include "evse_pilot.h" + +// Bits que auto-limpam passado um timeout +#define EVSE_ERR_AUTO_CLEAR_BITS ( \ + EVSE_ERR_DIODE_SHORT_BIT | \ + EVSE_ERR_TEMPERATURE_HIGH_BIT | \ + EVSE_ERR_RCM_TRIGGERED_BIT) + +// Error bits +#define EVSE_ERR_DIODE_SHORT_BIT (1 << 0) +#define EVSE_ERR_LOCK_FAULT_BIT (1 << 1) +#define EVSE_ERR_UNLOCK_FAULT_BIT (1 << 2) +#define EVSE_ERR_RCM_SELFTEST_FAULT_BIT (1 << 3) +#define EVSE_ERR_RCM_TRIGGERED_BIT (1 << 4) +#define EVSE_ERR_TEMPERATURE_HIGH_BIT (1 << 5) +#define EVSE_ERR_PILOT_FAULT_BIT (1 << 6) +#define EVSE_ERR_TEMPERATURE_FAULT_BIT (1 << 7) + +// Inicialização do módulo de erros +void evse_error_init(void); + +// Verificações e monitoramento +void evse_error_check(pilot_voltage_t pilot_voltage, bool is_n12v); +void evse_temperature_check(void); +void evse_error_tick(void); + +// Leitura e controle de erros +uint32_t evse_get_error(void); +void evse_error_set(uint32_t bitmask); +void evse_error_clear(uint32_t bitmask); + +bool evse_error_is_active(void); +uint32_t evse_error_get_bits(void); + +// ---------------------------------------------------- +// Semântica sticky: flag "todos erros limpos" +// ---------------------------------------------------- +// Fica true quando TODOS os erros são limpos. +// Volta a false assim que qualquer erro novo aparece. +// Permanece true até o consumidor limpar explicitamente. +bool evse_error_cleared_flag(void); +void evse_error_reset_flag(void); + +#endif // EVSE_ERROR_H +// === Fim de: components/evse/include/evse_error.h === + +// === Fim de: components/evse/include/evse_error.h === + + +// === Início de: components/evse/include/evse_session.h === +/* + * evse_session.h + * Module to track and retrieve charging session data (current or last completed), + * accumulating energy via periodic tick of instantaneous power. + */ + +#ifndef EVSE_SESSION_H +#define EVSE_SESSION_H + +#include +#include +#include "freertos/FreeRTOS.h" + +/** + * @brief Charging session statistics + */ +typedef struct { + TickType_t start_tick; ///< tick when session began + uint32_t duration_s; ///< total duration in seconds + uint32_t energy_wh; ///< total energy consumed in Wh + uint32_t avg_power_w; ///< average power in W + bool is_current; ///< true if session still in progress +} evse_session_t; + +/** + * @brief Initialize the session module + */ +void evse_session_init(void); + +/** + * @brief Mark the beginning of a charging session + */ +void evse_session_start(void); + +/** + * @brief Mark the end of the charging session and store it as "last session" + */ +void evse_session_end(void); + +/** + * @brief Periodic tick: must be called (e.g., each 1s) to accumulate energy from instant power + */ +void evse_session_tick(void); + +/** + * @brief Retrieve statistics of either the current ongoing session (if any) or + * the last completed session. + * @param out pointer to evse_session_t to be filled + * @return true if there is a current or last session available, false otherwise + */ +bool evse_session_get(evse_session_t *out); + +#endif // EVSE_SESSION_H + +// === Fim de: components/evse/include/evse_session.h === + + +// === Início de: components/evse/include/evse_meter.h === +#ifndef EVSE_METER_H +#define EVSE_METER_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define EVSE_METER_PHASE_COUNT 3 + +/// Inicializa o módulo EVSE Meter e registra os tratadores de eventos +void evse_meter_init(void); + +/// Retorna a potência instantânea (soma das 3 fases, em watts) +int evse_meter_get_instant_power(void); + +/// Retorna a energia total acumulada (em Wh) +int evse_meter_get_total_energy(void); + +/// Retorna as potências instantâneas nas fases L1, L2 e L3 (em watts) +void evse_meter_get_power(int power[EVSE_METER_PHASE_COUNT]); + +/// Retorna as tensões medidas nas fases L1, L2 e L3 (em volts) +void evse_meter_get_voltage(float voltage[EVSE_METER_PHASE_COUNT]); + +/// Retorna as correntes medidas nas fases L1, L2 e L3 (em amperes) +void evse_meter_get_current(float current[EVSE_METER_PHASE_COUNT]); + +/// Handler interno para eventos do medidor (não chamar externamente) +void evse_meter_on_meter_event(void* arg, void* event_data); + +#ifdef __cplusplus +} +#endif + +#endif // EVSE_METER_H + +// === Fim de: components/evse/include/evse_meter.h === + + +// === Início de: components/evse/include/evse_core.h === +#ifndef EVSE_CORE_H +#define EVSE_CORE_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Initializes the EVSE system and starts core task loop. + */ +void evse_init(void); + +#ifdef __cplusplus +} +#endif + +#endif // EVSE_CORE_H + +// === Fim de: components/evse/include/evse_core.h === + + +// === Início de: components/evse/include/evse_limits.h === +// === Início de: components/evse/include/evse_limits.h === +#ifndef EVSE_LIMITS_H +#define EVSE_LIMITS_H + +#include +#include +#include "evse_state.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// ============================ +// Limit Status & Evaluation +// ============================ + +/** + * @brief Sets the internal 'limit reached' flag. + */ +void evse_set_limit_reached(bool value); + +/** + * @brief Returns true if any runtime charging limit has been reached. + */ +bool evse_get_limit_reached(void); + +/** + * @brief Convenience alias for evse_get_limit_reached(). + */ +bool evse_is_limit_reached(void); + +/** + * @brief Checks if any session limit has been exceeded (energy, time or power). + * Should be called periodically during charging. + */ +void evse_limits_check(void); + +// ============================ +// Runtime Limit Configuration +// ============================ + +/** + * @brief Get/set energy consumption limit (in Wh). + */ +uint32_t evse_get_consumption_limit(void); +void evse_set_consumption_limit(uint32_t value); + +/** + * @brief Get/set maximum charging time (in seconds). + */ +uint32_t evse_get_charging_time_limit(void); +void evse_set_charging_time_limit(uint32_t value); + +/** + * @brief Get/set minimum acceptable power level (in Watts). + */ +uint16_t evse_get_under_power_limit(void); +void evse_set_under_power_limit(uint16_t value); + +#ifdef __cplusplus +} +#endif + +#endif // EVSE_LIMITS_H +// === Fim de: components/evse/include/evse_limits.h === + +// === Fim de: components/evse/include/evse_limits.h === + + +// === Início de: components/evse/include/evse_events.h === +#ifndef EVSE_EVENTS_H +#define EVSE_EVENTS_H + +#pragma once + +#include +#include +#include "esp_event.h" + +ESP_EVENT_DECLARE_BASE(EVSE_EVENTS); + +typedef enum { + EVSE_EVENT_INIT, + EVSE_EVENT_STATE_CHANGED, + EVSE_EVENT_CONFIG_UPDATED, + EVSE_EVENT_ENABLE_UPDATED, + EVSE_EVENT_AVAILABLE_UPDATED, + EVSE_EVENT_SESSION, +} evse_event_id_t; + +// ----------------- +// Eventos de STATE +// ----------------- +typedef enum { + EVSE_STATE_EVENT_IDLE, + EVSE_STATE_EVENT_WAITING, + EVSE_STATE_EVENT_CHARGING, + EVSE_STATE_EVENT_FAULT +} evse_state_event_t; + +typedef struct { + evse_state_event_t state; +} evse_state_event_data_t; + +// ----------------- +// Eventos de SESSÃO +// ----------------- +typedef enum { + EVSE_SESSION_EVENT_STARTED = 0, + EVSE_SESSION_EVENT_FINISHED, +} evse_session_event_type_t; + +typedef struct { + evse_session_event_type_t type; ///< STARTED / FINISHED + + // campos básicos da sessão, em tipos simples: + uint32_t session_id; ///< opcional, se tiveres um ID + uint32_t duration_s; ///< duração em segundos (0 no STARTED) + uint32_t energy_wh; ///< energia em Wh (0 no STARTED) + uint32_t avg_power_w; ///< potência média em W (0 no STARTED) + + bool is_current; ///< true se ainda estiver em curso +} evse_session_event_data_t; + + +// ----------------- +// Eventos de CONFIG +// ----------------- +typedef struct { + bool charging; // Estado de carregamento + float hw_max_current; // Corrente máxima suportada pelo hardware + float runtime_current; // Corrente de carregamento em uso + int64_t timestamp_us; // Momento da atualização +} evse_config_event_data_t; + +// Eventos simples e específicos +typedef struct { + bool enabled; // novo estado de enabled + int64_t timestamp_us; // epoch micros +} evse_enable_event_data_t; + +typedef struct { + bool available; // novo estado de available + int64_t timestamp_us; // epoch micros +} evse_available_event_data_t; + +#endif // EVSE_EVENTS_H + +// === Fim de: components/evse/include/evse_events.h === + + +// === Início de: components/evse/include/evse_api.h === +#ifndef EVSE_API_H +#define EVSE_API_H + +#include +#include +#include "evse_state.h" +#include "freertos/FreeRTOS.h" +#include "evse_session.h" + + +#ifdef __cplusplus +extern "C" { +#endif + +// =============================== +// Core EVSE State +// =============================== + +/** + * @brief Get current EVSE state (e.g., A, B1, C2). + */ +evse_state_t evse_get_state(void); + +/** + * @brief Set the EVSE state (e.g., called by FSM or hardware layer). + */ +void evse_set_state(evse_state_t state); + +// =============================== +// Charging Session Info +// =============================== + +/** + * @brief Retrieve statistics of either the current ongoing session (if any) + * or the last completed session. + * @param out pointer to evse_session_t to be filled + * @return true if there is a current or last session available + */ +bool evse_get_session(evse_session_t *out); + +// =============================== +// Charging Session Info +// =============================== + +/** + * @brief Returns true if the EV is charging (C1 or C2). + */ +bool evse_state_is_charging(evse_state_t state); + +/** + * @brief Returns true if the EV is connected (plugged). + */ +bool evse_state_is_plugged(evse_state_t state); + +/** + * @brief Returns true if a charging session is active (B2, C1, C2). + */ +bool evse_state_is_session(evse_state_t state); + +// =============================== +// Authorization +// =============================== + +/** + * @brief Set whether the vehicle is authorized to charge. + */ +void evse_state_set_authorized(bool authorized); + +/** + * @brief Get current authorization status. + */ +bool evse_state_get_authorized(void); + +// =============================== +// Configuration / Availability +// =============================== + +/** + * @brief Enable or disable the EVSE (software flag, persisted in NVS). + */ +void evse_set_enabled(bool value); + +/** + * @brief Returns true if the EVSE is currently available for use. + */ +bool evse_is_available(void); + +/** + * @brief Set EVSE availability flag (may be persisted in NVS). + */ +void evse_set_available(bool value); + +// =============================== +// Limit Status +// =============================== + +/** + * @brief Returns true if any runtime charging limit has been reached. + */ +bool evse_is_limit_reached(void); + +#ifdef __cplusplus +} +#endif + +#endif // EVSE_API_H +// === Fim de: components/evse/include/evse_api.h === + + +// === Início de: components/scheduler/include/scheduler_types.h === +// components/scheduler/include/scheduler_types.h +#pragma once +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + SCHED_MODE_DISABLED = 0, // não faz gating + SCHED_MODE_SIMPLE, // por ex: janela diária única + SCHED_MODE_WEEKLY // por ex: janelas por dia da semana +} sched_mode_t; + +typedef struct { + bool enabled; // gating ativo? + sched_mode_t mode; + + // exemplo bem simples: uma janela diária [start_min..end_min[ + // minutos desde meia-noite, 0..1439 + uint16_t start_min; + uint16_t end_min; +} sched_config_t; + +const char *sched_mode_to_str(sched_mode_t mode); +bool sched_mode_from_str(const char *s, sched_mode_t *out); + +#ifdef __cplusplus +} +#endif + +// === Fim de: components/scheduler/include/scheduler_types.h === + + +// === Início de: components/scheduler/include/scheduler_events.h === +// scheduler_events.h +#pragma once +#include "esp_event.h" +#include + +ESP_EVENT_DECLARE_BASE(SCHED_EVENTS); + +typedef enum +{ + SCHED_EVENT_INIT = 0, // envia estado inicial + SCHED_EVENT_WINDOW_CHANGED, // allowed_now mudou +} sched_event_id_t; typedef struct { - meter_event_data_t last_grid; - meter_event_data_t last_evse; - bool has_grid; - bool has_evse; - int64_t ts_grid_us; - int64_t ts_evse_us; - SemaphoreHandle_t mtx; -} meters_cache_t; + bool allowed_now; +} sched_event_state_t; -static meters_cache_t g_cache = {0}; +// === Fim de: components/scheduler/include/scheduler_events.h === -static void cache_init_once(void) -{ - if (!g_cache.mtx) - { - g_cache.mtx = xSemaphoreCreateMutex(); - } + +// === Início de: components/scheduler/include/scheduler.h === +// components/scheduler/include/scheduler.h +#pragma once + +#include +#include "scheduler_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void scheduler_init(void); +void scheduler_set_config(const sched_config_t *cfg); +sched_config_t scheduler_get_config(void); +bool scheduler_is_allowed_now(void); + +#ifdef __cplusplus } +#endif -static void copy_event_locked(const meter_event_data_t *src) -{ - if (!src) - return; - if (strcmp(src->source ? src->source : "", "EVSE") == 0) - { - g_cache.last_evse = *src; - g_cache.has_evse = true; - g_cache.ts_evse_us = esp_timer_get_time(); - } - else - { - // Default trate como GRID - g_cache.last_grid = *src; - g_cache.has_grid = true; - g_cache.ts_grid_us = esp_timer_get_time(); - } -} - -// ---------- Event handler (atualiza cache) ---------- -static void meters_event_handler(void *arg, esp_event_base_t base, int32_t id, void *data) -{ - if (base != METER_EVENT || id != METER_EVENT_DATA_READY || data == NULL) - return; - - cache_init_once(); - if (xSemaphoreTake(g_cache.mtx, pdMS_TO_TICKS(10)) == pdTRUE) - { - copy_event_locked((const meter_event_data_t *)data); - xSemaphoreGive(g_cache.mtx); - } -} - -// ---------- Helpers JSON ---------- -static cJSON *meter_event_to_json(const meter_event_data_t *m, int64_t ts_us) -{ - cJSON *root = cJSON_CreateObject(); - cJSON_AddStringToObject(root, "source", m->source ? m->source : "GRID"); - cJSON_AddNumberToObject(root, "frequency", m->frequency); - cJSON_AddNumberToObject(root, "powerFactor", m->power_factor); - cJSON_AddNumberToObject(root, "totalEnergy", m->total_energy); - - // timestamp relativo (segundos desde boot) e em micros - cJSON_AddNumberToObject(root, "timestampUs", (double)ts_us); - - // arrays - cJSON *vr = cJSON_CreateArray(); - for (int i = 0; i < 3; i++) - cJSON_AddItemToArray(vr, cJSON_CreateNumber(m->vrms[i])); - cJSON *ir = cJSON_CreateArray(); - for (int i = 0; i < 3; i++) - cJSON_AddItemToArray(ir, cJSON_CreateNumber(m->irms[i])); - cJSON *pw = cJSON_CreateArray(); - for (int i = 0; i < 3; i++) - cJSON_AddItemToArray(pw, cJSON_CreateNumber(m->watt[i])); - cJSON_AddItemToObject(root, "vrms", vr); - cJSON_AddItemToObject(root, "irms", ir); - cJSON_AddItemToObject(root, "watt", pw); - return root; -} - -// ---------- HTTP GET /api/v1/meters/live ---------- -static esp_err_t meters_live_get_handler(httpd_req_t *req) -{ - cache_init_once(); - - char query[64] = {0}; - char source[16] = {0}; - bool want_grid = true, want_evse = true; - - if (httpd_req_get_url_query_str(req, query, sizeof(query)) == ESP_OK) - { - if (httpd_query_key_value(query, "source", source, sizeof(source)) == ESP_OK) - { - if (strcasecmp(source, "GRID") == 0) - { - want_grid = true; - want_evse = false; - } - else if (strcasecmp(source, "EVSE") == 0) - { - want_grid = false; - want_evse = true; - } - } - } - - cJSON *out = cJSON_CreateObject(); - cJSON *arr = cJSON_CreateArray(); - - if (xSemaphoreTake(g_cache.mtx, pdMS_TO_TICKS(50)) == pdTRUE) - { - if (want_grid && g_cache.has_grid) - { - cJSON_AddItemToArray(arr, meter_event_to_json(&g_cache.last_grid, g_cache.ts_grid_us)); - } - if (want_evse && g_cache.has_evse) - { - cJSON_AddItemToArray(arr, meter_event_to_json(&g_cache.last_evse, g_cache.ts_evse_us)); - } - xSemaphoreGive(g_cache.mtx); - } - - cJSON_AddItemToObject(out, "meters", arr); - httpd_resp_set_type(req, "application/json"); - char *s = cJSON_PrintUnformatted(out); - httpd_resp_sendstr(req, s ? s : "{\"meters\":[]}"); - if (s) - free(s); - cJSON_Delete(out); - return ESP_OK; -} - -// ---------- Registro ---------- -void register_meters_data_handlers(httpd_handle_t server, void *ctx) -{ - // registra event handler para receber amostras - ESP_ERROR_CHECK(esp_event_handler_register(METER_EVENT, METER_EVENT_DATA_READY, meters_event_handler, NULL)); - - // endpoint GET - httpd_uri_t live = { - .uri = "/api/v1/meters/live", - .method = HTTP_GET, - .handler = meters_live_get_handler, - .user_ctx = ctx}; - httpd_register_uri_handler(server, &live); - - ESP_LOGI(TAG, "REST /api/v1/meters/live registrado"); -} - -// Função para recuperar as configurações dos contadores -static esp_err_t meters_config_get_handler(httpd_req_t *req) -{ - ESP_LOGI(TAG, "Received GET request for /api/v1/config/meters"); - - httpd_resp_set_type(req, "application/json"); - - cJSON *config = cJSON_CreateObject(); - - // Recuperando as configurações dos contadores - meter_type_t gridmeterType = meter_manager_grid_get_model(); - meter_type_t evsemeterType = meter_manager_evse_get_model(); - - ESP_LOGI(TAG, "Grid meter type: %s", meter_type_to_str(gridmeterType)); - ESP_LOGI(TAG, "EVSE meter type: %s", meter_type_to_str(evsemeterType)); - - // Adicionando os tipos de contadores ao objeto JSON - cJSON_AddStringToObject(config, "gridmeter", meter_type_to_str(gridmeterType)); - cJSON_AddStringToObject(config, "evsemeter", meter_type_to_str(evsemeterType)); - - // Convertendo o objeto JSON para uma string - const char *json_str = cJSON_Print(config); - ESP_LOGI(TAG, "Returning meters config: %s", json_str); - - httpd_resp_sendstr(req, json_str); - - // Liberação da memória - free((void *)json_str); - cJSON_Delete(config); - - return ESP_OK; -} - -// Função para atualizar as configurações dos contadores -static esp_err_t meters_config_post_handler(httpd_req_t *req) -{ - ESP_LOGI(TAG, "Received POST request for /api/v1/config/meters"); - - char buf[512]; - int len = httpd_req_recv(req, buf, sizeof(buf) - 1); - - if (len <= 0) - { - ESP_LOGE(TAG, "Received empty body in POST request"); - httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Empty body"); - return ESP_FAIL; - } - - buf[len] = '\0'; // Garantir que a string está terminada - - ESP_LOGI(TAG, "Received POST data: %s", buf); - - cJSON *json = cJSON_Parse(buf); - if (!json) - { - ESP_LOGE(TAG, "Failed to parse JSON data"); - // Resposta detalhada de erro - httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON format"); - return ESP_FAIL; - } - - // Atualizando os contadores - cJSON *gridmeter = cJSON_GetObjectItem(json, "gridmeter"); - if (gridmeter) - { - meter_type_t gridType = string_to_meter_type(gridmeter->valuestring); // Usando a função string_to_meter_type - ESP_LOGI(TAG, "Updating grid meter type to: %s", gridmeter->valuestring); - meter_manager_grid_set_model(gridType); - } - - cJSON *evsemeter = cJSON_GetObjectItem(json, "evsemeter"); - if (evsemeter) - { - meter_type_t evseType = string_to_meter_type(evsemeter->valuestring); // Usando a função string_to_meter_type - ESP_LOGI(TAG, "Updating EVSE meter type to: %s", evsemeter->valuestring); - meter_manager_evse_set_model(evseType); - } - - cJSON_Delete(json); - httpd_resp_sendstr(req, "Meters updated successfully"); - - ESP_LOGI(TAG, "Meters configuration updated successfully"); - - return ESP_OK; -} - -// Registrando os manipuladores de URI para os contadores -void register_meters_settings_handlers(httpd_handle_t server, void *ctx) -{ - ESP_LOGD(TAG, "Registering URI handlers for meters settings"); - - // URI para o método GET - httpd_uri_t meters_get_uri = { - .uri = "/api/v1/config/meters", - .method = HTTP_GET, - .handler = meters_config_get_handler, - .user_ctx = ctx}; - ESP_LOGD(TAG, "Registering GET handler for /api/v1/config/meters"); - httpd_register_uri_handler(server, &meters_get_uri); - - // URI para o método POST - httpd_uri_t meters_post_uri = { - .uri = "/api/v1/config/meters", - .method = HTTP_POST, - .handler = meters_config_post_handler, - .user_ctx = ctx}; - ESP_LOGD(TAG, "Registering POST handler for /api/v1/config/meters"); - httpd_register_uri_handler(server, &meters_post_uri); -} - -// === Fim de: components/rest_api/src/meters_settings_api.c === +// === Fim de: components/scheduler/include/scheduler.h === -// === Início de: components/rest_api/src/rest_main.c === -#include "rest_main.h" -#include "evse_settings_api.h" -#include "meters_settings_api.h" -#include "loadbalancing_settings_api.h" -#include "evse_link_config_api.h" -#include "network_api.h" -#include "ocpp_api.h" -#include "auth_api.h" -#include "dashboard_api.h" -#include "scheduler_settings_api.h" -#include "static_file_api.h" -#include "esp_log.h" +// === Início de: components/ocpp/include/ocpp_events.h === +#pragma once +#include "esp_event.h" +#include +#include -static const char *TAG = "rest_main"; +#ifdef __cplusplus +extern "C" { +#endif -esp_err_t rest_server_init(const char *base_path) -{ - ESP_LOGI(TAG, "Initializing REST API with base path: %s", base_path); +/* Base de eventos do OCPP (igual ao padrão usado em auth_events.h) */ +ESP_EVENT_DECLARE_BASE(OCPP_EVENTS); - rest_server_context_t *ctx = calloc(1, sizeof(rest_server_context_t)); - if (!ctx) - { - ESP_LOGE(TAG, "Failed to allocate memory for REST context"); - return ESP_ERR_NO_MEM; - } +/* IDs de eventos do OCPP */ +typedef enum { + OCPP_EVENT_CONNECTED = 0, // payload: const char* (server URL) – opcional + OCPP_EVENT_DISCONNECTED, // payload: NULL + OCPP_EVENT_AUTHORIZED, // payload: ocpp_idtag_event_t (opcional) + OCPP_EVENT_AUTH_REJECTED, // payload: ocpp_idtag_event_t (opcional) + OCPP_EVENT_AUTH_TIMEOUT, // payload: NULL + OCPP_EVENT_REMOTE_START, // payload: ocpp_idtag_event_t (opcional) + OCPP_EVENT_REMOTE_STOP, // payload: NULL + OCPP_EVENT_START_TX, // payload: ocpp_tx_event_t (opcional) + OCPP_EVENT_STOP_TX, // payload: ocpp_reason_event_t (opcional) + OCPP_EVENT_RESET, // payload: NULL + OCPP_EVENT_OPERATIVE_UPDATED +} ocpp_event_id_t; - strlcpy(ctx->base_path, base_path, sizeof(ctx->base_path)); +/* Limites de strings simples (evita dependência de auth.h) */ +#define OCPP_IDTAG_MAX 32 +#define OCPP_REASON_MAX 32 - httpd_config_t config = HTTPD_DEFAULT_CONFIG(); - config.uri_match_fn = httpd_uri_match_wildcard; - config.max_uri_handlers = 32; - - httpd_handle_t server = NULL; - esp_err_t err = httpd_start(&server, &config); - if (err != ESP_OK) - { - ESP_LOGE(TAG, "Failed to start HTTP server: %s", esp_err_to_name(err)); - free(ctx); - return err; - } - - ESP_LOGI(TAG, "HTTP server started successfully"); - - // Register endpoint groups - register_evse_settings_handlers(server, ctx); // Apenas chamando a função sem comparação - register_network_handlers(server, ctx); // Apenas chamando a função sem comparação - register_ocpp_handlers(server, ctx); // Apenas chamando a função sem comparação - register_auth_handlers(server, ctx); // Apenas chamando a função sem comparação - register_dashboard_handlers(server, ctx); // Apenas chamando a função sem comparação - register_meters_settings_handlers(server, ctx); // Apenas chamando a função sem comparação - register_loadbalancing_settings_handlers(server, ctx); // Apenas chamando a função sem comparação - register_link_config_handlers(server, ctx); - register_meters_data_handlers(server, ctx); - register_scheduler_settings_handlers(server, ctx); - - register_static_file_handlers(server, ctx); // Apenas chamando a função sem comparação - - ESP_LOGI(TAG, "All REST API endpoint groups registered successfully"); - - return ESP_OK; -} - -// === Fim de: components/rest_api/src/rest_main.c === - - -// === Início de: components/rest_api/src/network_api.c === -// ========================= -// network_api.c -// ========================= - -#include "network_api.h" -#include "esp_log.h" -#include "cJSON.h" -#include "network.h" -#include "mqtt.h" - -static const char *TAG = "network_api"; +/* Payloads opcionais */ +typedef struct { + char idTag[OCPP_IDTAG_MAX]; +} ocpp_idtag_event_t; typedef struct { - bool enabled; - char ssid[33]; - char password[65]; -} wifi_task_data_t; - - -static void wifi_apply_config_task(void *param) { - wifi_task_data_t *data = (wifi_task_data_t *)param; - ESP_LOGI("wifi_task", "Applying Wi-Fi config in background task"); - wifi_set_config(data->enabled, data->ssid, data->password); - free(data); - vTaskDelete(NULL); -} - -static esp_err_t wifi_get_handler(httpd_req_t *req) { - ESP_LOGI(TAG, "Handling GET /api/v1/config/wifi"); - - httpd_resp_set_type(req, "application/json"); - - // Obter dados da NVS via wifi.c - bool enabled = wifi_get_enabled(); - char ssid[33] = {0}; - char password[65] = {0}; - - wifi_get_ssid(ssid); - wifi_get_password(password); - - // Criar JSON - cJSON *json = cJSON_CreateObject(); - cJSON_AddBoolToObject(json, "enabled", enabled); - cJSON_AddStringToObject(json, "ssid", ssid); - cJSON_AddStringToObject(json, "password", password); - - // Enviar resposta - char *response = cJSON_Print(json); - httpd_resp_sendstr(req, response); - - // Limpeza - free(response); - cJSON_Delete(json); - - return ESP_OK; -} - -static esp_err_t wifi_post_handler(httpd_req_t *req) { - ESP_LOGI(TAG, "Handling POST /api/v1/config/wifi"); - - char buf[512]; - int len = httpd_req_recv(req, buf, sizeof(buf) - 1); - if (len <= 0) return ESP_FAIL; - buf[len] = '\0'; - - cJSON *json = cJSON_Parse(buf); - if (!json) return ESP_FAIL; - - // Valores padrão - bool enabled = false; - const char *ssid = NULL; - const char *password = NULL; - - cJSON *j_enabled = cJSON_GetObjectItem(json, "enabled"); - if (cJSON_IsBool(j_enabled)) enabled = j_enabled->valueint; - - cJSON *j_ssid = cJSON_GetObjectItem(json, "ssid"); - if (cJSON_IsString(j_ssid)) ssid = j_ssid->valuestring; - - cJSON *j_password = cJSON_GetObjectItem(json, "password"); - if (cJSON_IsString(j_password)) password = j_password->valuestring; - - // Enviar resposta antes de alterar Wi-Fi - httpd_resp_sendstr(req, "Wi-Fi config atualizada com sucesso"); - - // Alocar struct para passar para a task - wifi_task_data_t *task_data = malloc(sizeof(wifi_task_data_t)); - if (!task_data) { - cJSON_Delete(json); - ESP_LOGE(TAG, "Memory allocation failed for Wi-Fi task"); - return ESP_ERR_NO_MEM; - } - - task_data->enabled = enabled; - strncpy(task_data->ssid, ssid ? ssid : "", sizeof(task_data->ssid)); - strncpy(task_data->password, password ? password : "", sizeof(task_data->password)); - - // Criar task normal com função C - xTaskCreate( - wifi_apply_config_task, - "wifi_config_task", - 4096, - task_data, - 3, - NULL - ); - - cJSON_Delete(json); - return ESP_OK; -} - - -static esp_err_t config_mqtt_get_handler(httpd_req_t *req) -{ - ESP_LOGI(TAG, "Handling GET /api/v1/config/mqtt"); - - httpd_resp_set_type(req, "application/json"); - - bool enabled = mqtt_get_enabled(); - char server[64] = {0}; - char base_topic[32] = {0}; - char username[32] = {0}; - char password[64] = {0}; - uint16_t periodicity = mqtt_get_periodicity(); - - mqtt_get_server(server); - mqtt_get_base_topic(base_topic); - mqtt_get_user(username); - mqtt_get_password(password); - - ESP_LOGI(TAG, "MQTT Config:"); - ESP_LOGI(TAG, " Enabled: %s", enabled ? "true" : "false"); - ESP_LOGI(TAG, " Server: %s", server); - ESP_LOGI(TAG, " Topic: %s", base_topic); - ESP_LOGI(TAG, " Username: %s", username); - ESP_LOGI(TAG, " Password: %s", password); - ESP_LOGI(TAG, " Periodicity: %d", periodicity); - - cJSON *config = cJSON_CreateObject(); - cJSON_AddBoolToObject(config, "enabled", enabled); - cJSON_AddStringToObject(config, "host", server); - cJSON_AddNumberToObject(config, "port", 1883); - cJSON_AddStringToObject(config, "username", username); - cJSON_AddStringToObject(config, "password", password); - cJSON_AddStringToObject(config, "topic", base_topic); - cJSON_AddNumberToObject(config, "periodicity", periodicity); - - const char *config_str = cJSON_Print(config); - httpd_resp_sendstr(req, config_str); - - free((void *)config_str); - cJSON_Delete(config); - return ESP_OK; -} - - -static esp_err_t config_mqtt_post_handler(httpd_req_t *req) -{ - ESP_LOGI(TAG, "Handling POST /api/v1/config/mqtt"); - - char buf[512]; - int len = httpd_req_recv(req, buf, sizeof(buf) - 1); - if (len <= 0) { - ESP_LOGE(TAG, "Failed to read request body"); - httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid request body"); - return ESP_FAIL; - } - buf[len] = '\0'; - ESP_LOGI(TAG, "Received JSON: %s", buf); - - cJSON *json = cJSON_Parse(buf); - if (!json) { - ESP_LOGE(TAG, "Invalid JSON format"); - httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON"); - return ESP_FAIL; - } - - bool enabled = false; - const char *host = NULL, *topic = NULL, *username = NULL, *password = NULL; - int periodicity = 30; - - if (cJSON_IsBool(cJSON_GetObjectItem(json, "enabled"))) - enabled = cJSON_GetObjectItem(json, "enabled")->valueint; - - cJSON *j_host = cJSON_GetObjectItem(json, "host"); - if (cJSON_IsString(j_host)) host = j_host->valuestring; - - cJSON *j_topic = cJSON_GetObjectItem(json, "topic"); - if (cJSON_IsString(j_topic)) topic = j_topic->valuestring; - - cJSON *j_user = cJSON_GetObjectItem(json, "username"); - if (cJSON_IsString(j_user)) username = j_user->valuestring; - - cJSON *j_pass = cJSON_GetObjectItem(json, "password"); - if (cJSON_IsString(j_pass)) password = j_pass->valuestring; - - cJSON *j_periodicity = cJSON_GetObjectItem(json, "periodicity"); - if (cJSON_IsNumber(j_periodicity)) periodicity = j_periodicity->valueint; - - ESP_LOGI(TAG, "Applying MQTT config:"); - ESP_LOGI(TAG, " Enabled: %s", enabled ? "true" : "false"); - ESP_LOGI(TAG, " Host: %s", host); - ESP_LOGI(TAG, " Topic: %s", topic); - ESP_LOGI(TAG, " Username: %s", username); - ESP_LOGI(TAG, " Password: %s", password); - ESP_LOGI(TAG, " Periodicity: %d", periodicity); - - esp_err_t err = mqtt_set_config(enabled, host, topic, username, password, periodicity); - if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to apply MQTT config (code %d)", err); - httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to apply config"); - cJSON_Delete(json); - return ESP_FAIL; - } - - httpd_resp_sendstr(req, "Configuração MQTT atualizada com sucesso"); - cJSON_Delete(json); - return ESP_OK; -} - - - -void register_network_handlers(httpd_handle_t server, void *ctx) { - httpd_uri_t wifi_get = { - .uri = "/api/v1/config/wifi", - .method = HTTP_GET, - .handler = wifi_get_handler, - .user_ctx = ctx - }; - httpd_register_uri_handler(server, &wifi_get); - - httpd_uri_t wifi_post = { - .uri = "/api/v1/config/wifi", - .method = HTTP_POST, - .handler = wifi_post_handler, - .user_ctx = ctx - }; - httpd_register_uri_handler(server, &wifi_post); - - // URI handler for getting MQTT config - httpd_uri_t config_mqtt_get_uri = { - .uri = "/api/v1/config/mqtt", - .method = HTTP_GET, - .handler = config_mqtt_get_handler, - .user_ctx = ctx - }; - httpd_register_uri_handler(server, &config_mqtt_get_uri); - - // URI handler for posting MQTT config - httpd_uri_t config_mqtt_post_uri = { - .uri = "/api/v1/config/mqtt", - .method = HTTP_POST, - .handler = config_mqtt_post_handler, - .user_ctx = ctx - }; - httpd_register_uri_handler(server, &config_mqtt_post_uri); -} - -// === Fim de: components/rest_api/src/network_api.c === - - -// === Início de: components/rest_api/src/dashboard_api.c === -#include "dashboard_api.h" -#include "esp_log.h" -#include "cJSON.h" -#include "evse_api.h" -#include "evse_error.h" -#include "evse_config.h" -#include "evse_limits.h" - - -static const char *TAG = "dashboard_api"; - -static esp_err_t dashboard_get_handler(httpd_req_t *req) { - httpd_resp_set_type(req, "application/json"); - - // Cria o objeto JSON principal do dashboard - cJSON *dashboard = cJSON_CreateObject(); - - // Status do sistema - evse_state_t state = evse_get_state(); - cJSON_AddStringToObject(dashboard, "status", evse_state_to_str(state)); - - // Carregador - informação do carregador 1 (adapte conforme necessário) - cJSON *chargers = cJSON_CreateArray(); - cJSON *charger1 = cJSON_CreateObject(); - cJSON_AddNumberToObject(charger1, "id", 1); - cJSON_AddStringToObject(charger1, "status", evse_state_to_str(state)); - cJSON_AddNumberToObject(charger1, "current", evse_get_runtime_charging_current()); - cJSON_AddNumberToObject(charger1, "maxCurrent", evse_get_max_charging_current()); - - // Calcular a potência com base na corrente (considerando 230V) - int power = (evse_get_runtime_charging_current()) * 230; - cJSON_AddNumberToObject(charger1, "power", power); - - cJSON_AddItemToArray(chargers, charger1); - cJSON_AddItemToObject(dashboard, "chargers", chargers); - - // Consumo e tempo de carregamento - cJSON_AddNumberToObject(dashboard, "energyConsumed", evse_get_consumption_limit()); - cJSON_AddNumberToObject(dashboard, "chargingTime", evse_get_charging_time_limit()); - - // Alertas - cJSON *alerts = cJSON_CreateArray(); - if (evse_is_limit_reached()) { - cJSON_AddItemToArray(alerts, cJSON_CreateString("Limite de consumo atingido.")); - } - if (!evse_config_is_available()) { - cJSON_AddItemToArray(alerts, cJSON_CreateString("Estação indisponível.")); - } - if (!evse_config_is_enabled()) { - cJSON_AddItemToArray(alerts, cJSON_CreateString("EVSE desativado.")); - } - cJSON_AddItemToObject(dashboard, "alerts", alerts); - - // Erros - uint32_t error_bits = evse_get_error(); - cJSON *errors = cJSON_CreateArray(); - if (error_bits & EVSE_ERR_DIODE_SHORT_BIT) cJSON_AddItemToArray(errors, cJSON_CreateString("Diodo curto-circuitado")); - if (error_bits & EVSE_ERR_LOCK_FAULT_BIT) cJSON_AddItemToArray(errors, cJSON_CreateString("Falha no travamento")); - if (error_bits & EVSE_ERR_UNLOCK_FAULT_BIT) cJSON_AddItemToArray(errors, cJSON_CreateString("Falha no destravamento")); - if (error_bits & EVSE_ERR_RCM_SELFTEST_FAULT_BIT) cJSON_AddItemToArray(errors, cJSON_CreateString("Falha no autoteste do RCM")); - if (error_bits & EVSE_ERR_RCM_TRIGGERED_BIT) cJSON_AddItemToArray(errors, cJSON_CreateString("RCM disparado")); - if (error_bits & EVSE_ERR_TEMPERATURE_HIGH_BIT) cJSON_AddItemToArray(errors, cJSON_CreateString("Temperatura elevada")); - if (error_bits & EVSE_ERR_PILOT_FAULT_BIT) cJSON_AddItemToArray(errors, cJSON_CreateString("Erro no sinal piloto")); - if (error_bits & EVSE_ERR_TEMPERATURE_FAULT_BIT) cJSON_AddItemToArray(errors, cJSON_CreateString("Falha no sensor de temperatura")); - cJSON_AddItemToObject(dashboard, "errors", errors); - - // Enviar resposta JSON - const char *json_str = cJSON_Print(dashboard); - httpd_resp_sendstr(req, json_str); - - // Liberar memória - free((void *)json_str); - cJSON_Delete(dashboard); - - return ESP_OK; -} - -void register_dashboard_handlers(httpd_handle_t server, void *ctx) { - httpd_uri_t uri = { - .uri = "/api/v1/dashboard", - .method = HTTP_GET, - .handler = dashboard_get_handler, - .user_ctx = ctx - }; - httpd_register_uri_handler(server, &uri); -} - -// === Fim de: components/rest_api/src/dashboard_api.c === - - -// === Início de: components/rest_api/src/auth_api.c === -// ========================= -// auth_api.c (nova interface por modo) -// ========================= - -#include "auth_api.h" -#include "auth.h" -#include "esp_log.h" -#include "cJSON.h" -#include - -static const char *TAG = "auth_api"; - -// ================================= -// Dummy user storage (static list) -// ================================= - -static struct { - char username[128]; -} users[10] = { {"admin"}, {"user1"} }; - -static int num_users = 2; - -// ================================= -// Helpers -// ================================= - -static void send_json(httpd_req_t *req, cJSON *json) { - char *str = cJSON_PrintUnformatted(json); - httpd_resp_set_type(req, "application/json"); - httpd_resp_sendstr(req, str ? str : "{}"); - if (str) free(str); - cJSON_Delete(json); -} - -static esp_err_t recv_body(httpd_req_t *req, char *buf, size_t buf_sz, int *out_len) { - int remaining = req->content_len; - int received = 0; - - if (remaining <= 0) { - *out_len = 0; - return ESP_OK; - } - - while (remaining > 0 && received < (int)(buf_sz - 1)) { - int chunk = remaining; - int space = (int)(buf_sz - 1 - received); - if (chunk > space) chunk = space; - - int ret = httpd_req_recv(req, buf + received, chunk); - if (ret <= 0) return ESP_FAIL; - - received += ret; - remaining -= ret; - } - buf[received] = '\0'; - *out_len = received; - return ESP_OK; -} - -// ================================= -// Auth Mode (NEW API) -// ================================= - -static esp_err_t auth_mode_get_handler(httpd_req_t *req) { - auth_mode_t mode = auth_get_mode(); - cJSON *json = cJSON_CreateObject(); - cJSON_AddStringToObject(json, "mode", auth_mode_to_str(mode)); - send_json(req, json); - return ESP_OK; -} - -static esp_err_t auth_mode_post_handler(httpd_req_t *req) { - char buf[256]; - int len = 0; - if (recv_body(req, buf, sizeof(buf), &len) != ESP_OK) { - httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Erro ao receber dados"); - return ESP_FAIL; - } - - cJSON *json = cJSON_ParseWithLength(buf, len); - if (!json) { - httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "JSON inválido"); - return ESP_FAIL; - } - - cJSON *mode_js = cJSON_GetObjectItem(json, "mode"); - if (!cJSON_IsString(mode_js) || mode_js->valuestring == NULL) { - cJSON_Delete(json); - httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Campo 'mode' inválido ou ausente"); - return ESP_FAIL; - } - - auth_mode_t mode; - if (!auth_mode_from_str(mode_js->valuestring, &mode)) { - cJSON_Delete(json); - httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Valor de 'mode' inválido (use: open|local|ocpp)"); - return ESP_FAIL; - } - - auth_set_mode(mode); - cJSON_Delete(json); - - cJSON *resp = cJSON_CreateObject(); - cJSON_AddStringToObject(resp, "mode", auth_mode_to_str(mode)); - send_json(req, resp); - return ESP_OK; -} - -// ================================= -/* Users (mock) */ -// ================================= - -static esp_err_t users_get_handler(httpd_req_t *req) { - cJSON *root = cJSON_CreateObject(); - cJSON *list = cJSON_CreateArray(); - for (int i = 0; i < num_users; ++i) { - cJSON *u = cJSON_CreateObject(); - cJSON_AddStringToObject(u, "username", users[i].username); - cJSON_AddItemToArray(list, u); - } - cJSON_AddItemToObject(root, "users", list); - send_json(req, root); - return ESP_OK; -} - -static esp_err_t users_post_handler(httpd_req_t *req) { - char buf[128]; - int len = 0; - if (recv_body(req, buf, sizeof(buf), &len) != ESP_OK || len <= 0) { - httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Body vazio"); - return ESP_FAIL; - } - - if (num_users < 10) { - strlcpy(users[num_users].username, buf, sizeof(users[num_users].username)); - num_users++; - httpd_resp_sendstr(req, "Usuário adicionado com sucesso"); - return ESP_OK; - } else { - httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Limite de usuários atingido"); - return ESP_FAIL; - } -} - -static esp_err_t users_delete_handler(httpd_req_t *req) { - char query[128]; - if (httpd_req_get_url_query_str(req, query, sizeof(query)) == ESP_OK) { - char username[128]; - if (httpd_query_key_value(query, "username", username, sizeof(username)) == ESP_OK) { - for (int i = 0; i < num_users; i++) { - if (strcmp(users[i].username, username) == 0) { - for (int j = i; j < num_users - 1; j++) { - users[j] = users[j + 1]; - } - num_users--; - httpd_resp_sendstr(req, "Usuário removido com sucesso"); - return ESP_OK; - } - } - } - } - httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Usuário não encontrado"); - return ESP_FAIL; -} - -// ================================= -// Tags (apenas úteis no modo LOCAL) -// ================================= - -static esp_err_t tags_get_handler(httpd_req_t *req) { - cJSON *root = cJSON_CreateObject(); - cJSON *list = cJSON_CreateArray(); - - int count = auth_get_tag_count(); - for (int i = 0; i < count; i++) { - const char *tag = auth_get_tag_by_index(i); - if (tag) cJSON_AddItemToArray(list, cJSON_CreateString(tag)); - } - - cJSON_AddItemToObject(root, "tags", list); - send_json(req, root); - return ESP_OK; -} - -static esp_err_t tags_delete_handler(httpd_req_t *req) { - char query[128]; - if (httpd_req_get_url_query_str(req, query, sizeof(query)) == ESP_OK) { - char tag[AUTH_TAG_MAX_LEN]; - if (httpd_query_key_value(query, "tag", tag, sizeof(tag)) == ESP_OK) { - if (auth_remove_tag(tag)) { - httpd_resp_sendstr(req, "Tag removida com sucesso"); - return ESP_OK; - } - } - } - httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Tag não encontrada ou inválida"); - return ESP_FAIL; -} - -static esp_err_t tags_register_handler(httpd_req_t *req) { - if (auth_get_mode() != AUTH_MODE_LOCAL_RFID) { - httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Registo de tags disponível apenas no modo LOCAL"); - return ESP_FAIL; - } - auth_wait_for_tag_registration(); - httpd_resp_sendstr(req, "Modo de registro de tag ativado"); - return ESP_OK; -} - -// ================================= -// Register All REST Endpoints -// ================================= - -void register_auth_handlers(httpd_handle_t server, void *ctx) { - // Auth mode - httpd_register_uri_handler(server, &(httpd_uri_t){ - .uri = "/api/v1/config/auth-mode", - .method = HTTP_GET, - .handler = auth_mode_get_handler, - .user_ctx = ctx - }); - httpd_register_uri_handler(server, &(httpd_uri_t){ - .uri = "/api/v1/config/auth-mode", - .method = HTTP_POST, - .handler = auth_mode_post_handler, - .user_ctx = ctx - }); - - // Users (mock) - httpd_register_uri_handler(server, &(httpd_uri_t){ - .uri = "/api/v1/config/users", - .method = HTTP_GET, - .handler = users_get_handler, - .user_ctx = ctx - }); - httpd_register_uri_handler(server, &(httpd_uri_t){ - .uri = "/api/v1/config/users", - .method = HTTP_POST, - .handler = users_post_handler, - .user_ctx = ctx - }); - httpd_register_uri_handler(server, &(httpd_uri_t){ - .uri = "/api/v1/config/users", - .method = HTTP_DELETE, - .handler = users_delete_handler, - .user_ctx = ctx - }); - - // Tags - httpd_register_uri_handler(server, &(httpd_uri_t){ - .uri = "/api/v1/config/tags", - .method = HTTP_GET, - .handler = tags_get_handler, - .user_ctx = ctx - }); - httpd_register_uri_handler(server, &(httpd_uri_t){ - .uri = "/api/v1/config/tags", - .method = HTTP_DELETE, - .handler = tags_delete_handler, - .user_ctx = ctx - }); - httpd_register_uri_handler(server, &(httpd_uri_t){ - .uri = "/api/v1/config/tags/register", - .method = HTTP_POST, - .handler = tags_register_handler, - .user_ctx = ctx - }); -} - -// === Fim de: components/rest_api/src/auth_api.c === - - -// === Início de: components/rest_api/src/loadbalancing_settings_api.c === -#include "loadbalancing_settings_api.h" -#include "loadbalancer.h" -#include "esp_log.h" -#include "cJSON.h" - -static const char *TAG = "loadbalancing_settings_api"; - -// GET Handler: Retorna configurações atuais de load balancing -static esp_err_t loadbalancing_config_get_handler(httpd_req_t *req) { - bool enabled = loadbalancer_is_enabled(); - uint8_t currentLimit = load_balancing_get_max_grid_current(); - - ESP_LOGI(TAG, "Fetching load balancing settings: enabled = %d, currentLimit = %u", enabled, currentLimit); - - httpd_resp_set_type(req, "application/json"); - - cJSON *config = cJSON_CreateObject(); - cJSON_AddBoolToObject(config, "loadBalancingEnabled", enabled); - cJSON_AddNumberToObject(config, "loadBalancingCurrentLimit", currentLimit); - - const char *json_str = cJSON_Print(config); - httpd_resp_sendstr(req, json_str); - - ESP_LOGI(TAG, "Returned config: %s", json_str); - - free((void *)json_str); - cJSON_Delete(config); - return ESP_OK; -} - -// POST Handler: Atualiza configurações de load balancing -static esp_err_t loadbalancing_config_post_handler(httpd_req_t *req) { - char buf[512]; - int len = httpd_req_recv(req, buf, sizeof(buf) - 1); - - if (len <= 0) { - ESP_LOGE(TAG, "Received empty POST body"); - httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Empty body"); - return ESP_FAIL; - } - - buf[len] = '\0'; - ESP_LOGI(TAG, "Received POST data: %s", buf); - - cJSON *json = cJSON_Parse(buf); - if (!json) { - ESP_LOGE(TAG, "Invalid JSON"); - httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON"); - return ESP_FAIL; - } - - // Atualizar estado habilitado - cJSON *enabled_item = cJSON_GetObjectItem(json, "loadBalancingEnabled"); - if (enabled_item && cJSON_IsBool(enabled_item)) { - bool isEnabled = cJSON_IsTrue(enabled_item); - loadbalancer_set_enabled(isEnabled); - ESP_LOGI(TAG, "Updated loadBalancingEnabled to: %d", isEnabled); - } - - // Atualizar limite de corrente - cJSON *limit_item = cJSON_GetObjectItem(json, "loadBalancingCurrentLimit"); - if (limit_item && cJSON_IsNumber(limit_item)) { - uint8_t currentLimit = (uint8_t)limit_item->valuedouble; - - // Validar intervalo - if (currentLimit < 6 || currentLimit > 100) { - ESP_LOGW(TAG, "Rejected invalid currentLimit: %d", currentLimit); - cJSON_Delete(json); - httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid currentLimit (must be 6-100)"); - return ESP_FAIL; - } - - esp_err_t err = load_balancing_set_max_grid_current(currentLimit); - if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to save currentLimit: %s", esp_err_to_name(err)); - cJSON_Delete(json); - httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to save setting"); - return ESP_FAIL; - } - - ESP_LOGI(TAG, "Updated loadBalancingCurrentLimit to: %d", currentLimit); - } - - cJSON_Delete(json); - httpd_resp_sendstr(req, "Load balancing settings updated successfully"); - return ESP_OK; -} - -// Registro dos handlers na API HTTP -void register_loadbalancing_settings_handlers(httpd_handle_t server, void *ctx) { - // GET - httpd_uri_t get_uri = { - .uri = "/api/v1/config/loadbalancing", - .method = HTTP_GET, - .handler = loadbalancing_config_get_handler, - .user_ctx = ctx - }; - httpd_register_uri_handler(server, &get_uri); - - // POST - httpd_uri_t post_uri = { - .uri = "/api/v1/config/loadbalancing", - .method = HTTP_POST, - .handler = loadbalancing_config_post_handler, - .user_ctx = ctx - }; - httpd_register_uri_handler(server, &post_uri); -} - -// === Fim de: components/rest_api/src/loadbalancing_settings_api.c === - - -// === Início de: components/rest_api/src/evse_settings_api.c === -// ========================= -// evse_settings_api.c -// ========================= -#include "evse_settings_api.h" -#include "evse_api.h" -#include "evse_config.h" -#include "esp_log.h" -#include "cJSON.h" - -static const char *TAG = "evse_settings_api"; - -static esp_err_t config_settings_get_handler(httpd_req_t *req) { - httpd_resp_set_type(req, "application/json"); - cJSON *config = cJSON_CreateObject(); - cJSON_AddNumberToObject(config, "currentLimit", evse_get_max_charging_current()); - cJSON_AddNumberToObject(config, "temperatureLimit", evse_get_temp_threshold()); - const char *json_str = cJSON_Print(config); - httpd_resp_sendstr(req, json_str); - free((void *)json_str); - cJSON_Delete(config); - return ESP_OK; -} - -static esp_err_t config_settings_post_handler(httpd_req_t *req) { - char buf[512]; - int len = httpd_req_recv(req, buf, sizeof(buf) - 1); - if (len <= 0) { - httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Empty body"); - return ESP_FAIL; - } - buf[len] = '\0'; - cJSON *json = cJSON_Parse(buf); - if (!json) { - httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON"); - return ESP_FAIL; - } - - cJSON *current = cJSON_GetObjectItem(json, "currentLimit"); - if (current) evse_set_max_charging_current(current->valueint); - cJSON *temp = cJSON_GetObjectItem(json, "temperatureLimit"); - if (temp) evse_set_temp_threshold(temp->valueint); - - cJSON_Delete(json); - httpd_resp_sendstr(req, "Configurações atualizadas com sucesso"); - return ESP_OK; -} - -void register_evse_settings_handlers(httpd_handle_t server, void *ctx) { - httpd_uri_t get_uri = { - .uri = "/api/v1/config/settings", - .method = HTTP_GET, - .handler = config_settings_get_handler, - .user_ctx = ctx - }; - httpd_register_uri_handler(server, &get_uri); - - httpd_uri_t post_uri = { - .uri = "/api/v1/config/settings", - .method = HTTP_POST, - .handler = config_settings_post_handler, - .user_ctx = ctx - }; - httpd_register_uri_handler(server, &post_uri); -} - -// === Fim de: components/rest_api/src/evse_settings_api.c === - - -// === Início de: components/rest_api/include/dashboard_api.h === -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include "esp_http_server.h" - -/** - * @brief Registra o handler da dashboard (status geral do sistema) - */ -void register_dashboard_handlers(httpd_handle_t server, void *ctx); + int tx_id; // se disponível +} ocpp_tx_event_t; + +typedef struct { + char reason[OCPP_REASON_MAX]; +} ocpp_reason_event_t; + +// Payload do novo evento +typedef struct { + bool operative; // true = Operative, false = Inoperative + int64_t timestamp_us; // esp_timer_get_time() +} ocpp_operative_event_t; #ifdef __cplusplus } #endif -// === Fim de: components/rest_api/include/dashboard_api.h === +// === Fim de: components/ocpp/include/ocpp_events.h === -// === Início de: components/rest_api/include/scheduler_settings_api.h === -// ========================= -// scheduler_settings_api.h -// ========================= -#pragma once +// === Início de: components/ocpp/include/ocpp.h === +#ifndef OCPP_H_ +#define OCPP_H_ -#ifdef __cplusplus -extern "C" { -#endif - -#include "esp_http_server.h" - -/** - * @brief Registra os handlers de configuração do scheduler - * - * Endpoints: - * GET /api/v1/config/scheduler - * POST /api/v1/config/scheduler - */ -void register_scheduler_settings_handlers(httpd_handle_t server, void *ctx); - -#ifdef __cplusplus -} -#endif - -// === Fim de: components/rest_api/include/scheduler_settings_api.h === - - -// === Início de: components/rest_api/include/static_file_api.h === -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include "esp_http_server.h" - -/** - * @brief Registra o handler para servir arquivos estáticos da web (SPA) - */ -void register_static_file_handlers(httpd_handle_t server, void *ctx); - -#ifdef __cplusplus -} -#endif - -// === Fim de: components/rest_api/include/static_file_api.h === - - -// === Início de: components/rest_api/include/network_api.h === -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include "esp_http_server.h" - -/** - * @brief Registra os handlers de configuração Wi-Fi e MQTT - */ -void register_network_handlers(httpd_handle_t server, void *ctx); - -#ifdef __cplusplus -} -#endif - -// === Fim de: components/rest_api/include/network_api.h === - - -// === Início de: components/rest_api/include/auth_api.h === -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include "esp_http_server.h" - -/** - * @brief Registra os handlers de autenticação e gerenciamento de usuários - */ -void register_auth_handlers(httpd_handle_t server, void *ctx); - -#ifdef __cplusplus -} -#endif - -// === Fim de: components/rest_api/include/auth_api.h === - - -// === Início de: components/rest_api/include/loadbalancing_settings_api.h === -// ========================= -// loadbalancing_settings_api.h -// ========================= - -#ifndef LOADBALANCING_SETTINGS_API_H -#define LOADBALANCING_SETTINGS_API_H - -#include "esp_err.h" -#include "esp_http_server.h" - -// Função para registrar os manipuladores de URI para as configurações de load balancing e solar -void register_loadbalancing_settings_handlers(httpd_handle_t server, void *ctx); - -#endif // LOADBALANCING_SETTINGS_API_H - -// === Fim de: components/rest_api/include/loadbalancing_settings_api.h === - - -// === Início de: components/rest_api/include/rest_main.h === -#pragma once - -#include -#include - -#define SCRATCH_BUFSIZE (10240) - -typedef struct rest_server_context { - char base_path[ESP_VFS_PATH_MAX + 1]; - char scratch[SCRATCH_BUFSIZE]; -} rest_server_context_t; - -esp_err_t rest_server_init(const char *base_path); - -// === Fim de: components/rest_api/include/rest_main.h === - - -// === Início de: components/rest_api/include/meters_settings_api.h === -// ========================= -// meters_settings_api.h -// ========================= - -#ifndef METERS_SETTINGS_API_H -#define METERS_SETTINGS_API_H - -#include "esp_err.h" -#include "esp_http_server.h" - -// Função para registrar os manipuladores de URI para as configurações dos contadores -void register_meters_settings_handlers(httpd_handle_t server, void *ctx); - -/** - * Exponha dados do meter via REST. - * Endpoints: - * GET /api/v1/meters/live -> dados mais recentes (GRID e/ou EVSE) - * GET /api/v1/meters/live?source=GRID|EVSE - */ -void register_meters_data_handlers(httpd_handle_t server, void *ctx); - -#endif // METERS_SETTINGS_API_H - -// === Fim de: components/rest_api/include/meters_settings_api.h === - - -// === Início de: components/rest_api/include/ocpp_api.h === -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include "esp_http_server.h" - -/** - * @brief Registra os handlers da configuração e status do OCPP - */ -void register_ocpp_handlers(httpd_handle_t server, void *ctx); - -#ifdef __cplusplus -} -#endif - -// === Fim de: components/rest_api/include/ocpp_api.h === - - -// === Início de: components/rest_api/include/evse_link_config_api.h === -// ========================= -// evse_link_config_api.h -// ========================= - -#ifndef EVSE_LINK_CONFIG_API_H -#define EVSE_LINK_CONFIG_API_H - -#include "esp_err.h" -#include "esp_http_server.h" +#include +#include #ifdef __cplusplus extern "C" { #endif /** - * @brief Registra os manipuladores HTTP para configuração do EVSE-Link. - * - * Isso adiciona endpoints GET e POST em /api/v1/config/link - * para inspecionar e atualizar as configurações de link - * (habilitado, modo, self ID). - * - * @param server Handle do servidor HTTP - * @param ctx Contexto do usuário passado aos handlers + * @brief Start OCPP */ -void register_link_config_handlers(httpd_handle_t server, void *ctx); - -#ifdef __cplusplus -} -#endif - -#endif // EVSE_LINK_CONFIG_API_H - -// === Fim de: components/rest_api/include/evse_link_config_api.h === - - -// === Início de: components/rest_api/include/evse_settings_api.h === -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include "esp_http_server.h" +void ocpp_start(void); /** - * @brief Registra os handlers de configuração elétrica e limites de carregamento + * @brief Stop OCPP */ -void register_evse_settings_handlers(httpd_handle_t server, void *ctx); +void ocpp_stop(void); + +/* Config getters / setters */ +bool ocpp_get_enabled(void); +void ocpp_set_enabled(bool value); + +void ocpp_get_server(char *value); // buffer >= 64 +void ocpp_set_server(char *value); + +void ocpp_get_charge_id(char *value); // buffer >= 64 +void ocpp_set_charge_id(char *value); + +/* Estado de conexão */ +bool ocpp_is_connected(void); #ifdef __cplusplus } #endif -// === Fim de: components/rest_api/include/evse_settings_api.h === +#endif /* OCPP_H_ */ + +// === Fim de: components/ocpp/include/ocpp.h === diff --git a/readproject.py b/readproject.py index 29edba1..49f39c3 100644 --- a/readproject.py +++ b/readproject.py @@ -2,7 +2,7 @@ import os TAMANHO_MAX = 100000 # Limite por arquivo -def coletar_arquivos(diretorios, extensoes=(".c", ".h")): +def coletar_arquivos(diretorios, extensoes=(".h")): arquivos = [] for diretorio in diretorios: for raiz, pastas, nomes_arquivos in os.walk(diretorio): @@ -53,7 +53,7 @@ def unir_em_partes(arquivos, prefixo="projeto_parte", limite=TAMANHO_MAX): def main(): diretorio_main = "main" componentes_escolhidos = [ - "rest_api" + "meter_manager", "loadbalancer", "auth", "evse", "scheduler", "ocpp" ] diretorios_componentes = [os.path.join("components", nome) for nome in componentes_escolhidos]