diff --git a/bthome_phy6222/web/chart.css b/bthome_phy6222/web/chart.css new file mode 100644 index 0000000..df834cd --- /dev/null +++ b/bthome_phy6222/web/chart.css @@ -0,0 +1,118 @@ +/** + * Default styles for the dygraphs charting library. + */ +.dygraph-legend { + position: relative; + font-size: 12px; + z-index: 10; + width: 250px; /* divLabelsWidth */ + /* + dygraphs determines these based on the presence of chart labels. + It might make more sense to create a wrapper div around the chart proper. + top: 0px; + right: 2px; + */ + background: white; + line-height: normal; + text-align: center; + overflow: hidden; +} + +/* styles for a solid line in the legend */ +.dygraph-legend-line { + display: inline-block; + position: relative; + bottom: .5ex; + padding-left: 1em; + height: 1px; + border-bottom-width: 2px; + border-bottom-style: solid; + /* border-bottom-color is set based on the series color */ +} + +/* styles for a dashed line in the legend, e.g. when strokePattern is set */ +.dygraph-legend-dash { + display: inline-block; + position: relative; + bottom: .5ex; + height: 1px; + border-bottom-width: 2px; + border-bottom-style: solid; + /* border-bottom-color is set based on the series color */ + /* margin-right is set based on the stroke pattern */ + /* padding-left is set based on the stroke pattern */ +} + +.dygraph-roller { + position: absolute; + z-index: 10; +} + +/* This class is shared by all annotations, including those with icons */ +.dygraph-annotation { + position: absolute; + z-index: 10; + overflow: hidden; +} + +/* This class only applies to annotations without icons */ +/* Old class name: .dygraphDefaultAnnotation */ +.dygraph-default-annotation { + border: 1px solid black; + background-color: white; + text-align: center; +} + +.dygraph-axis-label { + /* position: absolute; */ + /* font-size: 14px; */ + z-index: 10; + line-height: normal; + overflow: hidden; + color: black; /* replaces old axisLabelColor option */ +} + +.dygraph-axis-label-x { +} + +.dygraph-axis-label-y { + color: green; +} + +.dygraph-axis-label-y2 { + color: blue; +} + +.dygraph-title { + font-weight: bold; + z-index: 10; + text-align: center; + /* font-size: based on titleHeight option */ +} + +.dygraph-xlabel { + text-align: center; + /* font-size: based on xLabelHeight option */ +} + +/* For y-axis label */ +.dygraph-label-rotate-left { + text-align: center; + /* See http://caniuse.com/#feat=transforms2d */ + transform: rotate(90deg); + -webkit-transform: rotate(90deg); + -moz-transform: rotate(90deg); + -o-transform: rotate(90deg); + -ms-transform: rotate(90deg); +} + +/* For y2-axis label */ +.dygraph-label-rotate-right { + text-align: center; + /* See http://caniuse.com/#feat=transforms2d */ + transform: rotate(-90deg); + -webkit-transform: rotate(-90deg); + -moz-transform: rotate(-90deg); + -o-transform: rotate(-90deg); + -ms-transform: rotate(-90deg); +} diff --git a/bthome_phy6222/web/dygraph.min.js b/bthome_phy6222/web/dygraph.min.js new file mode 100644 index 0000000..9b3376c --- /dev/null +++ b/bthome_phy6222/web/dygraph.min.js @@ -0,0 +1,2 @@ +/*! @license https://github.com/danvk/dygraphs/blob/v2.2.1/LICENSE.txt (MIT) */ +!function(t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).Dygraph=t()}(function(){var t=function n(i,r,o){function s(e,t){if(!r[e]){if(!i[e]){var a="function"==typeof require&&require;if(!t&&a)return a(e,!0);if(l)return l(e,!0);throw(t=new Error("Cannot find module '"+e+"'")).code="MODULE_NOT_FOUND",t}a=r[e]={exports:{}},i[e][0].call(a.exports,function(t){return s(i[e][1][t]||t)},a,a.exports,n,i,r,o)}return r[e].exports}for(var l="function"==typeof require&&require,t=0;th[o][2]&&(o=a));var s=h[r],l=h[o];h.splice(t,h.length-t),r2*e.width_||D.default.FORCE_FAST_PROXY)&&(p=E._fastCanvasProxy(p)),[]);m.hasNext;)if(O=m.next(),T.isOK(O.y)||_){if(l){if(!A&&L==O.xval)continue;var S,A=!1,L=O.xval,M=void 0===(S=d[O.canvasx])?x:i?S[0]:S,C=[O.canvasy,M];_?-1===w[0]?d[O.canvasx]=[O.canvasy,x]:d[O.canvasx]=[O.canvasy,w[0]]:d[O.canvasx]=O.canvasy}else C=isNaN(O.canvasy)&&_?[r.y+r.h,x]:[O.canvasy,x];isNaN(b)?(p.moveTo(O.canvasx,C[1]),p.lineTo(O.canvasx,C[0])):(_&&p.lineTo(O.canvasx,w[0]),p.lineTo(O.canvasx,C[0]),l&&(P.push([b,w[1]]),i&&S?P.push([O.canvasx,S[1]]):P.push([O.canvasx,C[1]]))),w=C,b=O.canvasx}else u(p,b,w[1],P),P=[],b=NaN,null===O.y_stacked||isNaN(O.y_stacked)||(d[O.canvasx]=r.h*O.y_stacked+r.y);i=_,C&&O&&(u(p,O.canvasx,C[1],P),P=[]),p.fill()}}}},a.default=E,e.exports=a.default},{"./dygraph":"dygraphs/src/dygraph.js","./dygraph-utils":"dygraphs/src/dygraph-utils.js"}],"dygraphs/src/dygraph-default-attrs.js":[function(t,e,a){"use strict";Object.defineProperty(a,"__esModule",{value:!0}),a.default=void 0;var n=l(t("./dygraph-tickers")),i=o(t("./dygraph-interaction-model")),r=o(t("./dygraph-canvas")),t=l(t("./dygraph-utils"));function o(t){return t&&t.__esModule?t:{default:t}}function s(t){var e,a;return"function"!=typeof WeakMap?null:(e=new WeakMap,a=new WeakMap,(s=function(t){return t?a:e})(t))}function l(t,e){if(!e&&t&&t.__esModule)return t;if(null===t||"object"!=typeof t&&"function"!=typeof t)return{default:t};e=s(e);if(e&&e.has(t))return e.get(t);var a,n,i={},r=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(a in t)"default"!==a&&Object.prototype.hasOwnProperty.call(t,a)&&((n=r?Object.getOwnPropertyDescriptor(t,a):null)&&(n.get||n.set)?Object.defineProperty(i,a,n):i[a]=t[a]);return i.default=t,e&&e.set(t,i),i}i={highlightCircleSize:3,highlightSeriesOpts:null,highlightSeriesBackgroundAlpha:.5,highlightSeriesBackgroundColor:"rgb(255, 255, 255)",labelsSeparateLines:!1,labelsShowZeroValues:!0,labelsKMB:!1,labelsKMG2:!1,showLabelsOnHighlight:!0,digitsAfterDecimal:2,maxNumberWidth:6,sigFigs:null,strokeWidth:1,strokeBorderWidth:0,strokeBorderColor:"white",axisTickSize:3,axisLabelFontSize:14,rightGap:5,showRoller:!1,xValueParser:void 0,delimiter:",",sigma:2,errorBars:!1,fractions:!1,wilsonInterval:!0,customBars:!1,fillGraph:!1,fillAlpha:.15,connectSeparatedPoints:!1,stackedGraph:!1,stackedGraphNaNFill:"all",hideOverlayOnMouseOut:!0,resizable:"no",legend:"onmouseover",legendFollowOffsetX:50,legendFollowOffsetY:-50,stepPlot:!1,xRangePad:0,yRangePad:null,drawAxesAtZero:!1,titleHeight:28,xLabelHeight:18,yLabelWidth:18,axisLineColor:"black",axisLineWidth:.3,gridLineWidth:.3,axisLabelWidth:50,gridLineColor:"rgb(128,128,128)",interactionModel:i.default.defaultModel,animatedZooms:!1,animateBackgroundFade:!0,showRangeSelector:!1,rangeSelectorHeight:40,rangeSelectorPlotStrokeColor:"#808FAB",rangeSelectorPlotFillGradientColor:"white",rangeSelectorPlotFillColor:"#A7B1C4",rangeSelectorBackgroundStrokeColor:"gray",rangeSelectorBackgroundLineWidth:1,rangeSelectorPlotLineWidth:1.5,rangeSelectorForegroundStrokeColor:"black",rangeSelectorForegroundLineWidth:1,rangeSelectorAlpha:.6,showInRangeSelector:null,plotter:[r.default._fillPlotter,r.default._errorPlotter,r.default._linePlotter],plugins:[],axes:{x:{pixelsPerLabel:70,axisLabelWidth:60,axisLabelFormatter:t.dateAxisLabelFormatter,valueFormatter:t.dateValueFormatter,drawGrid:!0,drawAxis:!0,independentTicks:!0,ticker:n.dateTicker},y:{axisLabelWidth:50,pixelsPerLabel:30,valueFormatter:t.numberValueFormatter,axisLabelFormatter:t.numberAxisLabelFormatter,drawGrid:!0,drawAxis:!0,independentTicks:!0,ticker:n.numericTicks},y2:{axisLabelWidth:50,pixelsPerLabel:30,valueFormatter:t.numberValueFormatter,axisLabelFormatter:t.numberAxisLabelFormatter,drawAxis:!0,drawGrid:!1,independentTicks:!1,ticker:n.numericTicks}}};a.default=i,e.exports=a.default},{"./dygraph-canvas":"dygraphs/src/dygraph-canvas.js","./dygraph-interaction-model":"dygraphs/src/dygraph-interaction-model.js","./dygraph-tickers":"dygraphs/src/dygraph-tickers.js","./dygraph-utils":"dygraphs/src/dygraph-utils.js"}],"dygraphs/src/dygraph-gviz.js":[function(t,e,a){"use strict";Object.defineProperty(a,"__esModule",{value:!0}),a.default=void 0;var n=(t=t("./dygraph"))&&t.__esModule?t:{default:t};function i(t){this.container=t}i.prototype.draw=function(t,e){this.container.innerHTML="",void 0!==this.date_graph&&this.date_graph.destroy(),this.date_graph=new n.default(this.container,t,e)},i.prototype.setSelection=function(t){var e=!1;t.length&&(e=t[0].row),this.date_graph.setSelection(e)},i.prototype.getSelection=function(){var t=[],e=this.date_graph.getSelection();if(!(e<0))for(var a=this.date_graph.layout_.points,n=0;na.boundedDates[1]&&(n=(t-=n-a.boundedDates[1])+a.dateRange),e.getOptionForAxis("logscale","x")?e.dateWindow_=[Math.pow(g.LOG_SCALE,t),Math.pow(g.LOG_SCALE,n)]:e.dateWindow_=[t,n],a.is2DPan)for(var i=a.dragEndY-a.dragStartY,r=0;r=o?u={tickValue:p,pixel_coord:g}:f.label="",s.push(f)}s.reverse()}}if(0===s.length){for(var _,y,v,x,d=n("labelsKMG2")?(_=[1,2,4,8,16,32,64,128,256],16):(_=[1,2,5,10,20,50,100],10),m=Math.ceil(a/o),m=Math.abs(e-t)/m,m=Math.floor(Math.log(m)/Math.log(d)),b=Math.pow(d,m),w=0;w<_.length&&(y=b*_[w],v=Math.floor(t/y)*y,x=Math.ceil(e/y)*y,!(oe?s:t(e,a,n,s+1,r)}return-1},e.cancelEvent=function(t){(t=t||window.event).stopPropagation&&t.stopPropagation();t.preventDefault&&t.preventDefault();return t.cancelBubble=!0,t.cancel=!0,t.returnValue=!1},e.clone=function t(e){var a=[];for(var n=0;n=h.Granularity.DECADAL?""+n:e>=h.Granularity.MONTHLY?E[i]+" "+n:0===3600*o+60*s+l+.001*a||e>=h.Granularity.DAILY?d(r)+" "+E[i]:eh.Granularity.MINUTELY?g(o,s,l,0):g(o,s,l,a)},e.dateParser=function(t){var e,a;if((-1==t.search("-")||-1!=t.search("T")||-1!=t.search("Z"))&&(a=l(t))&&!isNaN(a))return a;if(-1!=t.search("-")){for(e=t.replace("-","/","g");-1!=e.search("-");)e=e.replace("-","/");a=l(e)}else a=l(t);a&&!isNaN(a)||console.error("Couldn't parse "+t+" as a date");return a},e.dateStrToMillis=l,e.dateString_=s,e.dateValueFormatter=function(t,e){return s(t,e("labelsUTC"))},e.detectLineDelimiter=function(t){for(var e=0;e=Math.pow(10,i)?t.toExponential(n):f(t/s,n)+h[l]}else if(r<1){for(l=0;l=Math.pow(10,i)||rt.length)&&(e=t.length);for(var a=0,n=new Array(e);a=this.axes_.length?null:[(t=this.axes_[t]).computedValueRange[0],t.computedValueRange[1]]},L.prototype.yAxisRanges=function(){for(var t=[],e=0;e=this.rawData_.length||e<0||e>=this.rawData_[t].length?null:this.rawData_[t][e]},L.prototype.createInterface_=function(){var t,e=this.maindiv_,n=(this.graphDiv=document.createElement("div"),this.graphDiv.style.textAlign="left",this.graphDiv.style.position="relative",e.appendChild(this.graphDiv),this.canvas_=M.createCanvas(),this.canvas_.style.position="absolute",this.canvas_.style.top=0,this.canvas_.style.left=0,this.hidden_=this.createPlotKitCanvas_(this.canvas_),this.canvas_ctx_=M.getContext(this.canvas_),this.hidden_ctx_=M.getContext(this.hidden_),this.resizeElements_(),this.graphDiv.appendChild(this.hidden_),this.graphDiv.appendChild(this.canvas_),this.mouseEventElement_=this.createMouseEventElement_(),this.layout_=new i.default(this),this);this.mouseMoveHandler_=function(t){n.mouseMove_(t)},this.mouseOutHandler_=function(t){var e=t.target||t.fromElement,a=t.relatedTarget||t.toElement;M.isNodeContainedBy(e,n.graphDiv)&&!M.isNodeContainedBy(a,n.graphDiv)&&n.mouseOut_(t)},this.addAndTrackEvent(window,"mouseout",this.mouseOutHandler_),this.addAndTrackEvent(this.mouseEventElement_,"mousemove",this.mouseMoveHandler_),this.resizeHandler_||(this.resizeHandler_=function(t){n.resize()},this.addAndTrackEvent(window,"resize",this.resizeHandler_),this.resizeObserver_=null,t=this.getStringOption("resizable"),"undefined"==typeof ResizeObserver&&"no"!==t&&(console.error("ResizeObserver unavailable; ignoring resizable property"),t="no"),"horizontal"===t||"vertical"===t||"both"===t?e.style.resize=t:"passive"!==t&&(t="no"),"no"!==t&&(window.getComputedStyle(e).overflow,"visible"===window.getComputedStyle(e).overflow&&(e.style.overflow="hidden"),this.resizeObserver_=new ResizeObserver(this.resizeHandler_),this.resizeObserver_.observe(e)))},L.prototype.resizeElements_=function(){this.graphDiv.style.width=this.width_+"px",this.graphDiv.style.height=this.height_+"px";var t=this.getNumericOption("pixelRatio"),e=t||M.getContextPixelRatio(this.canvas_ctx_),e=(this.canvas_.width=this.width_*e,this.canvas_.height=this.height_*e,this.canvas_.style.width=this.width_+"px",this.canvas_.style.height=this.height_+"px",1!==e&&this.canvas_ctx_.scale(e,e),t||M.getContextPixelRatio(this.hidden_ctx_));this.hidden_.width=this.width_*e,this.hidden_.height=this.height_*e,this.hidden_.style.width=this.width_+"px",this.hidden_.style.height=this.height_+"px",1!==e&&this.hidden_ctx_.scale(e,e)},L.prototype.destroy=function(){this.canvas_ctx_.restore(),this.hidden_ctx_.restore();for(var t=this.plugins_.length-1;0<=t;t--){var e=this.plugins_.pop();e.plugin.destroy&&e.plugin.destroy()}function a(t){for(;t.hasChildNodes();)a(t.firstChild),t.removeChild(t.firstChild)}function n(t){for(var e in t)"object"==typeof t[e]&&(t[e]=null)}this.removeTrackedEvents_(),M.removeEvent(window,"mouseout",this.mouseOutHandler_),M.removeEvent(this.mouseEventElement_,"mousemove",this.mouseMoveHandler_),this.resizeObserver_&&(this.resizeObserver_.disconnect(),this.resizeObserver_=null),M.removeEvent(window,"resize",this.resizeHandler_),this.resizeHandler_=null,a(this.maindiv_);n(this.layout_),n(this.plotter_),n(this)},L.prototype.createPlotKitCanvas_=function(t){var e=M.createCanvas();return e.style.position="absolute",e.style.top=t.style.top,e.style.left=t.style.left,e.width=this.width_,e.height=this.height_,e.style.width=this.width_+"px",e.style.height=this.height_+"px",e},L.prototype.createMouseEventElement_=function(){return this.canvas_},L.prototype.setColors_=function(){for(var t,e,a,n=this.getLabels(),i=n.length-1,r=(this.colors_=[],this.colorsMap_={},this.getNumericOption("colorSaturation")||1),o=this.getNumericOption("colorValue")||.5,s=Math.ceil(i/2),l=this.getOption("colors"),h=this.visibility(),d=0;d=u.length||(o=u[d],M.isValidPoint(o)&&(s=o.canvasy,t>o.canvasx&&1+da[1]&&(a[1]=c),c=h&&null===u&&(u=p),l[p][0]<=d&&(c=p);for(var g=u=null===u?0:u,f=!0;f&&0=a.length?console.warn("Invalid series number in setVisibility: "+i):a[i]=t[i]);else for(i=0;i=a.length?console.warn("Invalid series number in setVisibility: "+i):a[i]=t[i]:t[i]<0||t[i]>=a.length?console.warn("Invalid series number in setVisibility: "+t[i]):a[t[i]]=e;this.predraw_()},L.prototype.size=function(){return{width:this.width_,height:this.height_}},L.prototype.setAnnotations=function(t,e){this.annotations_=t,this.layout_?(this.layout_.setAnnotations(this.annotations_),e||this.predraw_()):console.warn("Tried to setAnnotations before dygraph was ready. Try setting them in a ready() block. See dygraphs.com/tests/annotation.html")},L.prototype.annotations=function(){return this.annotations_},L.prototype.getLabels=function(){var t=this.attr_("labels");return t?t.slice():null},L.prototype.indexFromSetName=function(t){return this.setIndexByName_[t]},L.prototype.getRowForX=function(t){for(var e=0,a=this.numRows()-1;e<=a;){var n=a+e>>1,i=this.getValue(n,0);if(ii.x+i.w||_.canvasyi.y+i.h||(h=6,(l=_.annotation).hasOwnProperty("tickHeight")&&(h=l.tickHeight),(d=document.createElement("div")).style.fontSize=r.getOption("axisLabelFontSize")+"px",g="dygraph-annotation",l.hasOwnProperty("icon")||(g+=" dygraphDefaultAnnotation dygraph-default-annotation"),l.hasOwnProperty("cssClass")&&(g+=" "+l.cssClass),d.className=g,g=l.hasOwnProperty("width")?l.width:16,u=l.hasOwnProperty("height")?l.height:16,l.hasOwnProperty("icon")?((c=document.createElement("img")).src=l.icon,c.width=g,c.height=u,d.appendChild(c)):_.annotation.hasOwnProperty("shortText")&&d.appendChild(document.createTextNode(_.annotation.shortText)),c=_.canvasx-g/2,d.style.left=c+"px",p=0,p=l.attachAtBottom?(f=i.y+i.h-u-h,o[c]?f-=o[c]:o[c]=0,o[c]+=h+u,f):_.canvasy-u-h,d.style.top=p+"px",d.style.width=g+"px",d.style.height=u+"px",d.title=_.annotation.text,d.style.color=r.colorsMap_[_.name],d.style.borderColor=r.colorsMap_[_.name],l.div=d,r.addAndTrackEvent(d,"click",n("clickHandler","annotationClickHandler",_)),r.addAndTrackEvent(d,"mouseover",n("mouseOverHandler","annotationMouseOverHandler",_)),r.addAndTrackEvent(d,"mouseout",n("mouseOutHandler","annotationMouseOutHandler",_)),r.addAndTrackEvent(d,"dblclick",n("dblClickHandler","annotationDblClickHandler",_)),a.appendChild(d),this.annotations_.push(d),(g=t.drawingContext).save(),g.strokeStyle=l.hasOwnProperty("tickColor")?l.tickColor:r.colorsMap_[_.name],g.lineWidth=l.hasOwnProperty("tickWidth")?l.tickWidth:r.getOption("strokeWidth"),g.beginPath(),l.attachAtBottom?(g.moveTo(_.canvasx,f=p+u),g.lineTo(_.canvasx,f+h)):(g.moveTo(_.canvasx,_.canvasy),g.lineTo(_.canvasx,_.canvasy-2-h)),g.closePath(),g.stroke(),g.restore())}},n.prototype.destroy=function(){this.detachLabels()},a.default=n,e.exports=a.default},{}],"dygraphs/src/plugins/axes.js":[function(t,e,a){"use strict";Object.defineProperty(a,"__esModule",{value:!0}),a.default=void 0;var b=function(t,e){if(!e&&t&&t.__esModule)return t;if(null===t||"object"!=typeof t&&"function"!=typeof t)return{default:t};e=o(e);if(e&&e.has(t))return e.get(t);var a,n={},i=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(a in t){var r;"default"!==a&&Object.prototype.hasOwnProperty.call(t,a)&&((r=i?Object.getOwnPropertyDescriptor(t,a):null)&&(r.get||r.set)?Object.defineProperty(n,a,r):n[a]=t[a])}n.default=t,e&&e.set(t,n);return n}(t("../dygraph-utils"));function o(t){var e,a;return"function"!=typeof WeakMap?null:(e=new WeakMap,a=new WeakMap,(o=function(t){return t?a:e})(t))}function n(){this.xlabels_=[],this.ylabels_=[]}n.prototype.toString=function(){return"Axes Plugin"},n.prototype.activate=function(t){return{layout:this.layout,clearChart:this.clearChart,willDrawChart:this.willDrawChart}},n.prototype.layout=function(t){var e,a,n=t.dygraph;n.getOptionForAxis("drawAxis","y")&&(a=n.getOptionForAxis("axisLabelWidth","y")+2*n.getOptionForAxis("axisTickSize","y"),t.reserveSpaceLeft(a)),n.getOptionForAxis("drawAxis","x")&&(e=n.getOption("xAxisHeight")?n.getOption("xAxisHeight"):n.getOptionForAxis("axisLabelFontSize","x")+2*n.getOptionForAxis("axisTickSize","x"),t.reserveSpaceBottom(e)),2==n.numAxes()?n.getOptionForAxis("drawAxis","y2")&&(a=n.getOptionForAxis("axisLabelWidth","y2")+2*n.getOptionForAxis("axisTickSize","y2"),t.reserveSpaceRight(a)):2a&&(t=a-f("axisLabelWidth"),o.style.textAlign="right"),t<0&&(t=0,o.style.textAlign="left"),o.style.left=t+"px",o.style.width=f("axisLabelWidth")+"px",i.appendChild(o),c.xlabels_.push(o))})),e.strokeStyle=v.getOptionForAxis("axisLineColor","x"),e.lineWidth=v.getOptionForAxis("axisLineWidth","x"),e.beginPath(),y=v.getOption("drawAxesAtZero")?(_=v.toPercentYCoord(0,0),m(u.y+(_=1<_||_<0?1:_)*u.h)):m(u.y+u.h),e.moveTo(x(u.x),y),e.lineTo(x(u.x+u.w),y),e.closePath(),e.stroke()),e.restore())},a.default=n,e.exports=a.default},{"../dygraph-utils":"dygraphs/src/dygraph-utils.js"}],"dygraphs/src/plugins/chart-labels.js":[function(t,e,a){"use strict";Object.defineProperty(a,"__esModule",{value:!0}),a.default=void 0;function n(){this.title_div_=null,this.xlabel_div_=null,this.ylabel_div_=null,this.y2label_div_=null}function r(t){var e=document.createElement("div");return e.style.position="absolute",e.style.left=t.x+"px",e.style.top=t.y+"px",e.style.width=t.w+"px",e.style.height=t.h+"px",e}function o(t,e,a,n,i){var r=document.createElement("div");return r.style.position="absolute",r.style.left=1==a?"0px":e.x+"px",r.style.top=e.y+"px",r.style.width=e.w+"px",r.style.height=e.h+"px",r.style.fontSize=t.getOption("yLabelWidth")-2+"px",(t=document.createElement("div")).style.position="absolute",t.style.width=e.h+"px",t.style.height=e.w+"px",t.style.top=e.h/2-e.w/2+"px",t.style.left=e.w/2-e.h/2+"px",t.className="dygraph-label-rotate-"+(1==a?"right":"left"),(e=document.createElement("div")).className=n,e.innerHTML=i,t.appendChild(e),r.appendChild(t),r}n.prototype.toString=function(){return"ChartLabels Plugin"},n.prototype.activate=function(t){return{layout:this.layout,didDrawChart:this.didDrawChart}},n.prototype.detachLabels_=function(){for(var t=[this.title_div_,this.xlabel_div_,this.ylabel_div_,this.y2label_div_],e=0;ei.w&&(a=a-2*o-r-(l-i.x)),this.legend_div_.style.left=l+a+"px",this.legend_div_.style.top=n+"px"):"onmouseover"===h&&this.is_generated_div_&&(i=t.dygraph.plotter_.area,r=this.legend_div_.offsetWidth,this.legend_div_.style.left=i.x+i.w-r-1+"px",this.legend_div_.style.top=i.y+"px"))},m.prototype.deselect=function(t){"always"!==t.dygraph.getOption("legend")&&(this.legend_div_.style.display="none"),a=this.legend_div_,(n=document.createElement("span")).setAttribute("style","margin: 0; padding: 0 0 0 1em; border: 0;"),a.appendChild(n),e=n.offsetWidth,a.removeChild(n);var e,a=e,n=(this.one_em_width_=a,m.generateLegendHTML(t.dygraph,void 0,void 0,a,null));n instanceof Node&&n.nodeType===Node.DOCUMENT_FRAGMENT_NODE?(this.legend_div_.innerHTML="",this.legend_div_.appendChild(n)):this.legend_div_.innerHTML=n},m.prototype.didDrawChart=function(t){this.deselect(t)},m.prototype.predraw=function(t){var e;this.is_generated_div_&&(t.dygraph.graphDiv.appendChild(this.legend_div_),t=t.dygraph.plotter_.area,e=this.legend_div_.offsetWidth,this.legend_div_.style.left=t.x+t.w-e-1+"px",this.legend_div_.style.top=t.y+"px")},m.prototype.destroy=function(){this.legend_div_=null},m.generateLegendHTML=function(t,e,a,n,i){var r={dygraph:t,x:e,i:i,series:[]},o={},s=t.getLabels();if(s)for(var l=1;l');var n,i,r,o,s,l=0,h=0,d=[];for(n=0;n<=t.length;n++)l+=t[n%t.length];if(1<(s=Math.floor(a/(l-t[0])))){for(n=0;n');return u}(t.getOption("strokePattern",s[l]),h.color,n),label:s[l],labelHTML:s[l].replace(/&/g,"&").replace(/"/g,""").replace(//g,">"),isVisible:h.visible,color:h.color};r.series.push(d),o[s[l]]=d}if(void 0!==e){for(var u=t.optionsViewForAxis_("x"),c=u("valueFormatter"),p=(r.xHTML=c.call(t,e,u,s[0],t,i,0),[]),g=t.numAxes(),l=0;l":" "),n+="").concat(r.dashHTML," ").concat(r.labelHTML,""))}else{n=t.xHTML+":";for(var r,o,i=0;i"),o=r.isHighlighted?' class="highlight"':"",n+=" ").concat(r.labelHTML,": ").concat(r.yHTML,""))}return n},a.default=m,e.exports=a.default},{"../dygraph-utils":"dygraphs/src/dygraph-utils.js"}],"dygraphs/src/plugins/range-selector.js":[function(t,e,a){"use strict";Object.defineProperty(a,"__esModule",{value:!0}),a.default=void 0;var O=function(t,e){if(!e&&t&&t.__esModule)return t;if(null===t||"object"!=typeof t&&"function"!=typeof t)return{default:t};e=o(e);if(e&&e.has(t))return e.get(t);var a,n={},i=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(a in t){var r;"default"!==a&&Object.prototype.hasOwnProperty.call(t,a)&&((r=i?Object.getOwnPropertyDescriptor(t,a):null)&&(r.get||r.set)?Object.defineProperty(n,a,r):n[a]=t[a])}n.default=t,e&&e.set(t,n);return n}(t("../dygraph-utils")),A=n(t("../dygraph-interaction-model")),P=n(t("../iframe-tarp"));function n(t){return t&&t.__esModule?t:{default:t}}function o(t){var e,a;return"function"!=typeof WeakMap?null:(e=new WeakMap,a=new WeakMap,(o=function(t){return t?a:e})(t))}function i(){this.hasTouchInterface_="undefined"!=typeof TouchEvent,this.isMobileDevice_=/mobile|android/gi.test(navigator.appVersion),this.interfaceCreated_=!1}i.prototype.toString=function(){return"RangeSelector Plugin"},i.prototype.activate=function(t){return this.dygraph_=t,this.getOption_("showRangeSelector")&&this.createInterface_(),{layout:this.reserveSpace_,predraw:this.renderStaticLayer_,didDrawChart:this.renderInteractiveLayer_}},i.prototype.destroy=function(){this.bgcanvas_=null,this.fgcanvas_=null,this.leftZoomHandle_=null,this.rightZoomHandle_=null},i.prototype.getOption_=function(t,e){return this.dygraph_.getOption(t,e)},i.prototype.setDefaultOption_=function(t,e){this.dygraph_.attrs_[t]=e},i.prototype.createInterface_=function(){this.createCanvases_(),this.createZoomHandles_(),this.initInteraction_(),this.getOption_("animatedZooms")&&(console.warn("Animated zooms and range selector are not compatible; disabling animatedZooms."),this.dygraph_.updateOptions({animatedZooms:!1},!0)),this.interfaceCreated_=!0,this.addToGraph_()},i.prototype.addToGraph_=function(){var t=this.graphDiv_=this.dygraph_.graphDiv;t.appendChild(this.bgcanvas_),t.appendChild(this.fgcanvas_),t.appendChild(this.leftZoomHandle_),t.appendChild(this.rightZoomHandle_)},i.prototype.removeFromGraph_=function(){var t=this.graphDiv_;t.removeChild(this.bgcanvas_),t.removeChild(this.fgcanvas_),t.removeChild(this.leftZoomHandle_),t.removeChild(this.rightZoomHandle_),this.graphDiv_=null},i.prototype.reserveSpace_=function(t){this.getOption_("showRangeSelector")&&t.reserveSpaceBottom(this.getOption_("rangeSelectorHeight")+4)},i.prototype.renderStaticLayer_=function(){this.updateVisibility_()&&(this.resize_(),this.drawStaticLayer_())},i.prototype.renderInteractiveLayer_=function(){this.updateVisibility_()&&!this.isChangingRange_&&(this.placeZoomHandles_(),this.drawInteractiveLayer_())},i.prototype.updateVisibility_=function(){var t,e=this.getOption_("showRangeSelector");return e?this.interfaceCreated_?this.graphDiv_&&this.graphDiv_.parentNode||this.addToGraph_():this.createInterface_():this.graphDiv_&&(this.removeFromGraph_(),t=this.dygraph_,setTimeout(function(){t.width_=0,t.resize()},1)),e},i.prototype.resize_=function(){function t(t,e,a,n){n=n||O.getContextPixelRatio(e);t.style.top=a.y+"px",t.style.left=a.x+"px",t.width=a.w*n,t.height=a.h*n,t.style.width=a.w+"px",t.style.height=a.h+"px",1!=n&&e.scale(n,n)}var e=this.dygraph_.layout_.getPlotArea(),a=0,a=(this.dygraph_.getOptionForAxis("drawAxis","x")&&(a=this.getOption_("xAxisHeight")||this.getOption_("axisLabelFontSize")+2*this.getOption_("axisTickSize")),this.canvasRect_={x:e.x,y:e.y+e.h+a+4,w:e.w,h:this.getOption_("rangeSelectorHeight")},this.dygraph_.getNumericOption("pixelRatio"));t(this.bgcanvas_,this.bgcanvas_ctx_,this.canvasRect_,a),t(this.fgcanvas_,this.fgcanvas_ctx_,this.canvasRect_,a)},i.prototype.createCanvases_=function(){this.bgcanvas_=O.createCanvas(),this.bgcanvas_.className="dygraph-rangesel-bgcanvas",this.bgcanvas_.style.position="absolute",this.bgcanvas_.style.zIndex=9,this.bgcanvas_ctx_=O.getContext(this.bgcanvas_),this.fgcanvas_=O.createCanvas(),this.fgcanvas_.className="dygraph-rangesel-fgcanvas",this.fgcanvas_.style.position="absolute",this.fgcanvas_.style.zIndex=9,this.fgcanvas_.style.cursor="default",this.fgcanvas_ctx_=O.getContext(this.fgcanvas_)},i.prototype.createZoomHandles_=function(){var t=new Image;t.className="dygraph-rangesel-zoomhandle",t.style.position="absolute",t.style.zIndex=10,t.style.visibility="hidden",t.style.cursor="col-resize",t.width=9,t.height=16,t.src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAQCAYAAADESFVDAAAAAXNSR0IArs4c6QAAAAZiS0dEANAAzwDP4Z7KegAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAAd0SU1FB9sHGw0cMqdt1UwAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAaElEQVQoz+3SsRFAQBCF4Z9WJM8KCDVwownl6YXsTmCUsyKGkZzcl7zkz3YLkypgAnreFmDEpHkIwVOMfpdi9CEEN2nGpFdwD03yEqDtOgCaun7sqSTDH32I1pQA2Pb9sZecAxc5r3IAb21d6878xsAAAAAASUVORK5CYII=",this.isMobileDevice_&&(t.width*=2,t.height*=2),this.leftZoomHandle_=t,this.rightZoomHandle_=t.cloneNode(!1)},i.prototype.initInteraction_=function(){var i=this,e=document,r=0,n=null,o=!1,s=!1,l=!this.isMobileDevice_,a=new P.default,h=function(t){var e=i.dygraph_.xAxisExtremes(),a=(e[1]-e[0])/i.canvasRect_.w;return[e[0]+(t.leftHandlePos-i.canvasRect_.x)*a,e[0]+(t.rightHandlePos-i.canvasRect_.x)*a]},d=function(t){return O.cancelEvent(t),o=!0,r=t.clientX,n=t.target||t.srcElement,"mousedown"!==t.type&&"dragstart"!==t.type||(O.addEvent(e,"mousemove",u),O.addEvent(e,"mouseup",c)),i.fgcanvas_.style.cursor="col-resize",a.cover(),!0},u=function(t){if(!o)return!1;O.cancelEvent(t);var e,a=t.clientX-r;return Math.abs(a)<4||(r=t.clientX,t=i.getZoomHandleStatus_(),e=n==i.leftZoomHandle_?(e=t.leftHandlePos+a,e=Math.min(e,t.rightHandlePos-n.width-3),Math.max(e,i.canvasRect_.x)):(e=t.rightHandlePos+a,e=Math.min(e,i.canvasRect_.x+i.canvasRect_.w),Math.max(e,t.leftHandlePos+n.width+3)),a=n.width/2,n.style.left=e-a+"px",i.drawInteractiveLayer_(),l&&p()),!0},c=function(t){return!!o&&(o=!1,a.uncover(),O.removeEvent(e,"mousemove",u),O.removeEvent(e,"mouseup",c),i.fgcanvas_.style.cursor="default",l||p(),!0)},p=function(){try{var t,e=i.getZoomHandleStatus_();i.isChangingRange_=!0,e.isZoomed?(t=h(e),i.dygraph_.doZoomXDates_(t[0],t[1])):i.dygraph_.resetZoom()}finally{i.isChangingRange_=!1}},g=function(t){var e=i.leftZoomHandle_.getBoundingClientRect(),a=e.left+e.width/2,e=(e=i.rightZoomHandle_.getBoundingClientRect()).left+e.width/2;return t.clientX>a&&t.clientX=i.canvasRect_.x+i.canvasRect_.w?e=(t=i.canvasRect_.x+i.canvasRect_.w)-a:(e+=n,t+=n),a=i.leftZoomHandle_.width/2,i.leftZoomHandle_.style.left=e-a+"px",i.rightZoomHandle_.style.left=t-a+"px",i.drawInteractiveLayer_(),l&&v()),!0},y=function(t){return!!s&&(s=!1,O.removeEvent(e,"mousemove",_),O.removeEvent(e,"mouseup",y),l||v(),!0)},v=function(){try{i.isChangingRange_=!0,i.dygraph_.dateWindow_=h(i.getZoomHandleStatus_()),i.dygraph_.drawGraph_(!1)}finally{i.isChangingRange_=!1}},t=function(t){o||s||(t=g(t)?"move":"default")!=i.fgcanvas_.style.cursor&&(i.fgcanvas_.style.cursor=t)},x=function(t){"touchstart"==t.type&&1==t.targetTouches.length?d(t.targetTouches[0])&&O.cancelEvent(t):"touchmove"==t.type&&1==t.targetTouches.length?u(t.targetTouches[0])&&O.cancelEvent(t):c(t)},m=function(t){"touchstart"==t.type&&1==t.targetTouches.length?f(t.targetTouches[0])&&O.cancelEvent(t):"touchmove"==t.type&&1==t.targetTouches.length?_(t.targetTouches[0])&&O.cancelEvent(t):y(t)},b=function(t,e){for(var a=["touchstart","touchend","touchmove","touchcancel"],n=0;nthis.canvasRect_.x||t+1 -PHY62x2 BTHome v0.6 +PHY62x2 BTHome v0.99 + + + + + @@ -323,20 +22,26 @@ select { //BLE values const FLASH_SIZE = 0x80000; const OTA_MAX_SIZE = 0x30000; // 196608 + +const SERVICE_SCREEN = 0x00000020; +const SERVICE_TH_TRG = 0x00004000; + var bluetoothDevice, gattServer, otaCharacteristic, cmdCharacteristic, infoService; -var devinfo = {}; -var devsrs = {}; -var devcfg = {}; -var devcfs = {}; -var devtime = {}; -var devnm = {}; +var devInfo = {}; +var devSrv = { services: 0 }; +var devCfg = {}; +var devSens = {}; +var devTrig = {}; +var devTime = {}; +var devName = ""; +var isConnected = false; +var isChartEnabled = true; +var isMemoActive = false; -var startTime = 0, - isConnected = false, - flg_memo_act = false - flg_memo_cnt = 0; +var startTime = 0; +var memoCount = 0; var ota = { fwArray: null, @@ -371,7 +76,7 @@ function handleError(text) { if ((bluetoothDevice != null) && (connectRetries < 5)) { addLog("Переподключение " + connectRetries + " из " + 5); $('btnDisconnect').disabled = true; - $('btnReconnect').disabled = true; + $('btnReconnect').disabled = true; connectRetries++; doConnect(); } else { @@ -399,6 +104,7 @@ function connect() { .map((x) => ({ namePrefix: x })); } if (bluetoothDevice != null) bluetoothDevice.gatt.disconnect(); + chartData.length = 0; resetVariables(); bluetoothDevice = null; $('btnReconnect').disabled = true; @@ -433,7 +139,7 @@ function reconnect() { bluetoothDevice.gatt.disconnect(); isConnected = false; } - $('btnConnect').disabled = true; + $('btnConnect').disabled = true; $('btnReconnect').disabled = true; connectRetries = 0; doConnect(); @@ -451,30 +157,31 @@ function getStrCharacteristic(infosrv, suuid) { function getDevInfo(devInfEnabled) { return new Promise((resolve, reject) => { - devinfo.nrstr = null; - devinfo.srstr = null; - devinfo.frstr = null; - devinfo.hrstr = null; - devinfo.vrstr = null; + devInfo.nrstr = null; + devInfo.srstr = null; + devInfo.frstr = null; + devInfo.hrstr = null; + devInfo.vrstr = null; if (devInfEnabled == true) { gattServer.getPrimaryService(0x180a).then(service => { console.log("Найден Device Information Service"); infoService = service; return getStrCharacteristic(infoService, 0x2a24).then(value => { - devinfo.nrstr = value; - return getStrCharacteristic(infoService, 0x2a25).then(value => { - devinfo.srstr = value; + devInfo.nrstr = value; + //DOMException: getCharacteristic(s) called with blocklisted UUID. https://goo.gl/4NeimX + //return getStrCharacteristic(infoService, 0x2a25).then(value => { + // devInfo.srstr = value; return getStrCharacteristic(infoService, 0x2a26).then(value => { - devinfo.frstr = value; + devInfo.frstr = value; return getStrCharacteristic(infoService, 0x2a27).then(value => { - devinfo.hrstr = value; + devInfo.hrstr = value; return getStrCharacteristic(infoService, 0x2a28).then(value => { - devinfo.vrstr = value; - return resolve(devinfo.flg); + devInfo.vrstr = value; + return resolve(devInfo.flg); }).catch(error => { console.log(error); return resolve(null);}); }).catch(error => { console.log(error); return resolve(null);}); }).catch(error => { console.log(error); return resolve(null);}); - }).catch(error => { console.log(error); return resolve(null);}); + //}).catch(error => { console.log(error); return resolve(null);}); }).catch(error => { console.log(error); return resolve(null);}); }).catch(error => { console.log(error); return resolve(null);}); } else return resolve(null); @@ -487,37 +194,40 @@ function linkOta() { .then(characteristic => { console.log("Найдена OTA Characteristic"); otaCharacteristic = characteristic; - return otaCharacteristic.addEventListener('characteristicvaluechanged', event => OtaBlkParse(event.target.value)); + return otaCharacteristic.addEventListener('characteristicvaluechanged', event => parseBlkOTA(event.target.value)); }).then(_ => { return otaCharacteristic.readValue(); }).then(value => { - if(value.byteLength > 1) + if(value.byteLength > 1) { addLog("OTA ver: "+ hex(value.getUint8(1),2)); + $("btnStartDFU").value = "Старт программирования" + } return resolve(null);}); }).catch(error => { console.log(error); return resolve(null);}); } function phyConnect(info_flg) { return getDevInfo(info_flg).then(_ => { - if(devinfo.nrstr != null) - addLog("Model: "+devinfo.nrstr); - if(devinfo.srstr != null) - addLog("Serial: "+devinfo.srstr); - if(devinfo.frstr != null) - addLog("Firmware: "+devinfo.frstr); - if(devinfo.hrstr != null) - addLog("Hardware: "+devinfo.hrstr); - if(devinfo.vrstr != null) - addLog("Software: "+devinfo.vrstr); + if(devInfo.nrstr != null) + addLog("Model: "+devInfo.nrstr); + if(devInfo.srstr != null) + addLog("Serial: "+devInfo.srstr); + if(devInfo.frstr != null) + addLog("Firmware: "+devInfo.frstr); + if(devInfo.hrstr != null) + addLog("Hardware: "+devInfo.hrstr); + if(devInfo.vrstr != null) + addLog("Software: "+devInfo.vrstr); return gattServer.getPrimaryService(0xfcd2); }).then(service => { console.log("Найден Main Service"); mainService = service; + cmdCharacteristic = null; return mainService.getCharacteristic(0xfff4); }).then(characteristic => { console.log("Найдена CMD Characteristic"); cmdCharacteristic = characteristic; - return cmdCharacteristic.addEventListener('characteristicvaluechanged', event => customBlkParse(event.target.value)); + return cmdCharacteristic.addEventListener('characteristicvaluechanged', event => parseBlkCustom(event.target.value)); }).then(_ => { return cmdCharacteristic.startNotifications(); }).then(_ => { @@ -526,21 +236,30 @@ function phyConnect(info_flg) { if(value.byteLength >= 10) if(value.getUint8(0) != 0) addLog("Странный ответ устройства!"); + else if((value.getUint32(8, true) & 1) != 0) // SERVICE_OTA ? + return linkOta(); otaCharacteristic = null; - return linkOta(); }).then(_ => { showState("Устройство подключено."); isConnected = true; // $('btnConnect').disabled = true; $('btnDisconnect').disabled = false; - $('btnReconnect').disabled = false; + // $('btnReconnect').disabled = false; disableControls(false); + // selectConfigTab(); + // $('tabConfig').style.display = "block"; - if (otaCharacteristic != null && ota.fwArray != null) - $('btnStartDFU').disabled = false; - selectConfigTab(); - $('tabConfig').style.display = "block"; + let el = $('btnStartDFU'); + if (otaCharacteristic != null) { + el.disabled = ota.fwArray == null; + el.innerHTML = "Старт"; + el.title = "Старт программирования..." + } else { + el.innerHTML = "Режим OTA"; + el.title = "Переключение на BootLoader ..." + } + return cmdCharacteristic.writeValue(new Uint8Array([0x33])); // get measure }).catch(handleError); } @@ -570,8 +289,34 @@ function doConnect() { }).catch(handleError); } +function auxControls(state) +{ + if ( devSrv.services & SERVICE_SCREEN ) { + $('tblChkCfg').style.display = "block"; + $('tblComfort').style.display = "block"; + } else { + $('tblChkCfg').style.display = "none"; + $('tblComfort').style.display = "none"; + } + if ( devSrv.services & SERVICE_TH_TRG ) { + $('tblTrigger').style.display = "block"; + $('btnGetTrg').disabled = state; + $('btnSetTrg').disabled = state; + } else { + $('tblTrigger').style.display = "none"; + $('btnGetTrg').style.display = "none"; + $('btnSetTrg').style.display = "none"; + } + if (devSrv.services & (SERVICE_SCREEN|SERVICE_TH_TRG)) + $('hrPres').style.display = "block"; + else + $('hrPres').style.display = "none"; +} + function disableControls(state) { + //--debug devSrv.services = SERVICE_SCREEN|SERVICE_TH_TRG; + auxControls(state); if(state) { // hide $('hrSensorData').style.display = "none"; $('tblSensorData').style.display = "none"; @@ -581,37 +326,45 @@ function disableControls(state) } $('btnGetDev').disabled = state; $('btnSetDev').disabled = state; + $('btnRstDev').disabled = state; $('btnGetSens').disabled = state; $('btnSetSens').disabled = state; + $('btnRstSens').disabled = state; $('btnGetDevTime').disabled = state; $('btnSetDevTime').disabled = state; - $('btnReadFlash').disabled = state; - $('btnSaveFlash').disabled = state; - $('btnReadAddr').disabled = state; $('btnWriteAddr').disabled = state; $('btnSendCommand').disabled = state; + + $('btnGetMAC').disabled = state; + $('btnSetMAC').disabled = state; + + $('btnGetName').disabled = state; + $('btnSetName').disabled = state; + $('btnRstName').disabled = state; + } function onDisconnected() { isConnected = false; resetVariables(); showState('Устройство отключено.'); - $('btnConnect').disabled = false; + $('btnConnect').disabled = false; + $('btnReconnect').disabled = false; // $('tabConfig').style.display = "none"; disableControls(true); } function startDFU() { - if(otaCharacteristic != null && ota.ind.version == 1 && (devsrs.services & 1) == 1) { - addLog("Старт программирования..."); - updateBegin(); + if(otaCharacteristic != null && ota.ind.version == 1 && (devSrv.services & 1) == 1) { + addLog("Старт программирования..."); + updateBegin(); } else { addLog("Переключение на BootLoader..."); if(cmdCharacteristic != null) { cmdCharacteristic.writeValue(new Uint8Array([0x72,0x55])).then(_ => { - console.log('Send reboot to bootloader - ok'); + console.log('Comand to reboot to bootloader sent'); reconnect(); }).catch(error => { addLog(error); }); } @@ -632,6 +385,14 @@ function showState(text) { addLog(text); } +function showError(text) { +// console.log("Status: " + status); + let s = "Ошибка: " + text; + $("lblStatus").className = "shadowerror"; + $("lblStatus").innerHTML = s; + addLog(text); +} + function showProgress(text) { $("lblStatus").className = "shadowprogress"; $("lblStatus").innerHTML = text; @@ -641,14 +402,6 @@ function clearLog() { $("log").innerHTML = ""; } -function showError(text) { -// console.log("Status: " + status); - let s = "Ошибка: " + text; - $("lblStatus").className = "shadowerror"; - $("lblStatus").innerHTML = s; - addLog(text); -} - function updateFail(err) { let s = "OTA: " + err; showError(s); @@ -759,7 +512,7 @@ var crc32 = (function() { }; })(); -function testOTAFirmware(data) { +function testFwOTA(data) { ota.fwsize = 0; let fsize = data.byteLength; console.log("File size = 0x"+ fsize.toString(16)); @@ -802,7 +555,7 @@ function testOTAFirmware(data) { function getFwArray(data, filename) { addLog("Файл: " + filename); - let s = testOTAFirmware(data); + let s = testFwOTA(data); if(s != "OK") { $('btnStartDFU').disabled = true; addLog(s); @@ -828,7 +581,7 @@ function getFwArray(data, filename) { $('btnStartDFU').disabled = false; } -var ota_errors = [ +var otaErrStrs = [ 'OK', 'Неверная команда', 'Не задан старт', @@ -848,11 +601,11 @@ function get_msg_ota_err(err) { if(err == 255) return "OTA end"; if(err <= 11) - return ota_errors[err]; + return otaErrStrs[err]; return "Неизвестная ошибка"; } -function OtaBlkParse(value) { +function parseBlkOTA(value) { if(value.byteLength < 20) return; ota.ind = {}; ota.ind.err_flag = value.getUint8(0); @@ -865,7 +618,14 @@ function OtaBlkParse(value) { ota.ind.fw_value = value.getUint32(12,true); ota.ind.crc32 = value.getUint32(16,true); //console.log('otablk: '+dump8(value, value.byteLength)); - console.log('OTA read: ver: '+hex(ota.ind.version,2)+', err: '+ota.ind.err_flag+' - '+get_msg_ota_err(ota.ind.err_flag)+', dbg: '+ota.ind.debug_flag+', start: '+ota.ind.start_flag+', offs: 0x'+hex(ota.ind.prg_offset,8)+', idx: 0x'+hex(ota.ind.pkt_index,4)+', total: 0x'+hex(ota.ind.pkt_total,4)+', crc: 0x'+hex(ota.ind.crc32,8)); + console.log('OTA read: ver: ' + hex(ota.ind.version,2) + + ', err: ' + ota.ind.err_flag + ' - ' + get_msg_ota_err(ota.ind.err_flag) + + ', dbg: ' + ota.ind.debug_flag + + ', start: '+ota.ind.start_flag + + ', offs: 0x'+hex(ota.ind.prg_offset,8) + + ', idx: 0x'+hex(ota.ind.pkt_index,4) + + ', total: 0x'+hex(ota.ind.pkt_total,4) + + ', crc: 0x'+hex(ota.ind.crc32,8)); } function updateBegin() { @@ -874,7 +634,7 @@ function updateBegin() { return; } setTimeout(function() { - otaCharSend("00ff").then(_ => { otaCharacteristic.readValue().then(value => { + sendCharOTA("00ff").then(_ => { otaCharacteristic.readValue().then(value => { let cmd = "01ff"; // TODO // if(ota.ext_flg) { @@ -882,11 +642,11 @@ function updateBegin() { // cmd += ota.fwArray.substring(0,8); // fw_id // cmd += hex((ota.blockCount >> 8) | ((ota.blockCount & 0xff) << 8),4); // pkt_total // } - otaCharSend(cmd).then(_ => { otaCharacteristic.readValue().then(value => { + sendCharOTA(cmd).then(_ => { otaCharacteristic.readValue().then(value => { if(value.byteLength >= 2 && value.getUint8(0) == 0) { setTimeout(function() { startTime = new Date().getTime(); - sendOTAblock(0); + sendBlkOTA(0); }, 50); } else showError("Ошибка N"+value.getUint8(0)+" OTA!"); @@ -900,7 +660,7 @@ function updateBegin() { function sendLastOTA() { otaCharacteristic.readValue().then(value => { if(value.byteLength >= 1 && value.getUint8(0) == 0xff) { - return otaCharSend("02ff").then(_ => { + return sendCharOTA("02ff").then(_ => { let s = "Программирование завершено за " + (new Date().getTime() - startTime) / 1000 + " секунды"; showProgress(s); addLog(s); @@ -912,7 +672,7 @@ function sendLastOTA() { }).catch(function(err) { updateFail(err); }); } -function sendOTAblock(blockNr) { +function sendBlkOTA(blockNr) { if (blockNr >= ota.blockCount) { return sendLastOTA(); } @@ -920,7 +680,7 @@ function sendOTAblock(blockNr) { var blockNrString = getHexBLockCount(blockNr); var blockString = blockNrString + ota.fwArray.substring(blockNr * 32, blockNr * 32 + 32); var blockCRC = getHexCRC(blockString); - otaCharSend(blockString + blockCRC).then(_ => { + sendCharOTA(blockString + blockCRC).then(_ => { if (blockNr >= ota.blockCount - 1) { return sendLastOTA(); } @@ -928,7 +688,7 @@ function sendOTAblock(blockNr) { if ((blockNr + 1) % 16 == 0) { otaCharacteristic.readValue().then(value => { if(value.byteLength >= 20 && value.getUint8(0) == 0) - sendOTAblock(blockNr + 1); + sendBlkOTA(blockNr + 1); else { let s = get_msg_ota_err(value.getUint8(0)); if(s != "OK") @@ -936,7 +696,7 @@ function sendOTAblock(blockNr) { } }).catch(function(err) { updateFail(err); }); } else - sendOTAblock(blockNr + 1); + sendBlkOTA(blockNr + 1); }, 0); }).catch(function(err) { updateFail(err); @@ -949,7 +709,7 @@ function getHexBLockCount(count) { } -var otaCharSend = function(data) { +var sendCharOTA = function(data) { return new Promise(function(resolve, reject) { //console.log("OTA send: " + data); otaCharacteristic.writeValue(hexToBytes(data)).then(function(character) { @@ -960,7 +720,7 @@ var otaCharSend = function(data) { }); } -var mainCharSend = function(data, characteristic) { +var sendCharMain = function(data, characteristic) { return new Promise(function(resolve, reject) { console.log("Send: " + data); characteristic.writeValue(hexToBytes(data)).then(function(character) { @@ -1011,14 +771,14 @@ function writeCmd(data) { function readAddress() { if(cmdCharacteristic) { - let faddr = parseInt($('inputAddr').value, 16); + let faddr = parseInt($('inpAddr').value, 16); readAddr(faddr); } } function writeAddress() { - let addr = parseInt($('inputAddr').value, 16); - let data = hexToBytes($('inputData').value); + let addr = parseInt($('inpAddr').value, 16); + let data = hexToBytes($('inpData').value); if(data.length != 0 && data.length <= 16) writeAddr(addr, data); else @@ -1034,20 +794,51 @@ function sendCommand() { console.log('Должно быть от 1 до 20 hex байт!'); } -function customBlkParse(value) { +function showConfig() { +/* + if($('inpFlag') != null) + $('inpFlag').value = devCfg.flg; // пока не реализовано + $('inpTxPwr').value = devCfg.tx_power; // 0..0x3f -> -20..+5 dBm нелинейное 0x1f = +0 дБм + $('inpLat').value = (devCfg.connect_latency + 1)*30; // = (connect_latency + 1)*30 ms + + $('inpAdvInt').value = devCfg.advertising_interval*62.5; // *62.5 = интервала рекламы в ms + $('inpMeasInt').value = devCfg.measure_interval; // *devCfg.advertising_interval*62.5 = опрос датчика в ms, value минимум = 2 (интервала рекламы) + $('inpAverInt').value = devCfg.averaging_measurements; // запись истории: при 0 - отключена, 1...255 * шаг опроса датчика = интерал записи истории + $('inpBatInt').value = devCfg.batt_interval; // в секундах, минимум 2 секунды, но кратно интервалу рекламы +*/ + $('chkCfgNotify').checked = (devCfg.flg & 1) != 0; + $('chkCfgClock').checked = (devCfg.flg & 2) != 0; + $('chkCfgSmiley').checked = (devCfg.flg & 4) != 0; + $('chkCfgTrg').checked = (devCfg.flg & 8) != 0; + let txPwr = 31; + el = $('selTxPwr'); + for(let n = 0; n < el.options.length; n++) if(el.options[n].value >= devCfg.tx_power) txPwr = el.options[n].value; + el.value = txPwr; + $('inpLat').value = (devCfg.connect_latency + 1)*30; // = (connect_latency + 1)*30 ms + $('inpAdvInt').value = devCfg.advertising_interval*62.5; // *62.5 = интервала рекламы в ms + $('inpMeasInt').value = devCfg.measure_interval; // *devCfg.advertising_interval*62.5 = опрос датчика в ms, value минимум = 2 (интервала рекламы) + $('inpAverInt').value = devCfg.averaging_measurements; // запись истории: при 0 - отключена, 1...255 * шаг опроса датчика = интерал записи истории + $('inpBatInt').value = devCfg.batt_interval; // в секундах, минимум 2 секунды, но кратно интервалу рекламы +} + +function parseBlkCustom(value) { let len = value.byteLength; if(len == 0) return; let blkId = value.getUint8(0); - if((blkId == 0) && (len > 11)) { + if((blkId == 0x00) && (len >= 12)) { // CMD_ID_DEVID Get dev id, version, services - devsrs.revision = value.getUint8(1); - devsrs.hw_version = value.getUint16(2, true); - devsrs.sw_version = value.getUint16(4, true); - devsrs.dev_spec_data = value.getUint16(6, true); - devsrs.services = value.getUint32(8, true); - addLog("Dev info # hw: "+hex(devsrs.hw_version,4)+", sw: "+hex(devsrs.sw_version,4)+", services: "+hex(devsrs.services,8)+", sd: "+hex(devsrs.dev_spec_data, 4)); + devSrv.revision = value.getUint8(1); + devSrv.hw_version = value.getUint16(2, true); + devSrv.sw_version = value.getUint16(4, true); + devSrv.dev_spec_data = value.getUint16(6, true); + devSrv.services = value.getUint32(8, true); + auxControls(false); + addLog("Device info # hw: " + hex(devSrv.hw_version,4) + + ", sw: " + hex(devSrv.sw_version,4) + + ", services: " + hex(devSrv.services,8) + + ", sd: " + hex(devSrv.dev_spec_data, 4)); } else if(blkId == 0x35){ - if(flg_memo_act) { + if(isMemoActive) { if(len >= 12) { let cnt = value.getUint16(1, true); let tc = value.getUint32(3, true); @@ -1056,22 +847,22 @@ function customBlkParse(value) { let vb = value.getUint16(11, true); let dt = new Date(tc*1000); addLog(((dt.toISOString().slice(0, -1)).replace('T',' ')).replace('.000','')+' # Батарея: '+vb+' мВ , Температура: '+tm+'°C, Влажность: '+hm+'%, Счетчик: '+cnt); - flg_memo_cnt = cnt; + memoCount = cnt; } else if(len == 3) { let flg = value.getUint16(1, true); console.log('Memo End: '+flg); - if(flg_memo_cnt == 0) + if(memoCount == 0) addLog('Пока нет истории!'); - flg_memo_act = false; + isMemoActive = false; } else if(len == 2) { addLog('Нет сервиса записи истории!'); - flg_memo_act = false; + isMemoActive = false; } else { console.log('blk: ' + dump8(value, value.byteLength)); - flg_memo_act = false; + isMemoActive = false; } } - } else if(blkId == 0x33 && len > 9) { + } else if(blkId == 0x33 && len >= 10) { let count = value.getUint16(1, true); let temp = value.getInt16(3, true) / 100.0; let humi = value.getInt16(5, true) / 100.0; @@ -1079,79 +870,100 @@ function customBlkParse(value) { let pbat = value.getUint8(9); let flg = 0; let rds_count = 0; + let s = 'Температура: ' + temp.toFixed(2) + '°C' + + ', Влажность: ' + humi.toFixed(2) + '%' + + ', Vbat: ' + vbat + ' мВ' + + ', ID: ' + count; if(len > 12) { flg = value.getUint8(9); rds_count = value.getUint32(10, true); - s += ', счетчик срабатываний: '+ rds_count+', флаги: 0x'+hex(flg,2)+':r'+(flg&1)+'/t'+((flg>>1)&1); + s += ', счетчик срабатываний: '+ rds_count + + ', флаги: 0x' + hex(flg,2) + ':r' + (flg&1) + '/t' + ((flg>>1)&1); } + console.log(s); $('lblTemp').innerHTML = temp + ' °C'; $('lblHumi').innerHTML = humi + ' %RH'; - $('lblBat').innerHTML = pbat + ' % (' + vbat / 1000 + ' V)'; - // let s = 'Vbat: '+vbat+' мВ '+pbat+'%, Температура: '+temp.toFixed(2)+'°C, Влажность: '+humi.toFixed(2)+'%, ID: '+count; - let s = 'Температура: ' + temp.toFixed(2) + '°C, ' - + 'Влажность: ' + humi.toFixed(2) + '%, ' - + 'Vbat: ' + vbat + ' мВ, ' - + 'ID: ' + count + ', счетчик срабатываний: '+ rds_count + ', ' - + 'флаги: 0x' + hex(flg,2) + ':r' + (flg&1) + '/t' + ((flg>>1)&1); - addLog(s); + $('lblBat').innerHTML = pbat + ' % (' + vbat / 1000 + ' V)'; + if(len > 10) { + flg = value.getUint8(10); + s += ", флаги: 0x" + hex(flg,2); + $('lblTrg').innerHTML = "In: " + (flg&1) + + ", Out: " + ((flg&2)? 1:0) + + ", Cmf: " + ((flg&4)? 1:0) + + ", Te: " + ((flg&16)? 1:0) + + ", He: " + ((flg&32)? 1:0); + } + console.log(s); + addChartData(temp, humi); + if(isChartEnabled) + chart.updateOptions( + { + file: chartData, + axes: { + y: { 'valueRange': rangeT }, + y2: { 'valueRange': rangeH } + } + } + ); getDevTime(); } else if((blkId == 0x25 || blkId == 0x26) && (len > 12)) { // CMD_ID_CFS Get/Set sensor config - devcfs.temp_k = value.getUint32(1, true); - devcfs.humi_k = value.getUint32(5, true); - devcfs.temp_z = value.getInt16(9, true); - devcfs.humi_z = value.getInt16(11, true); + devSens.temp_k = value.getUint32(1, true); + devSens.humi_k = value.getUint32(5, true); + devSens.temp_z = value.getInt16(9, true); + devSens.humi_z = value.getInt16(11, true); - $('inputTempK').value = devcfs.temp_k; - $('inputHumK').value = devcfs.humi_k; - $('inputTempZ').value = devcfs.temp_z; - $('inputHumZ').value = devcfs.humi_z; + $('inpTempK').value = devSens.temp_k; + $('inpHumK').value = devSens.humi_k; + $('inpTempZ').value = devSens.temp_z; + $('inpHumZ').value = devSens.humi_z; - addLog("Dev sensor # tk: "+devcfs.temp_k+", hk: "+devcfs.humi_k+", tz: "+devcfs.temp_z+", hz: "+devcfs.humi_z); + addLog("Device Sensor " + + "# Kt: " + devSens.temp_k + + ", Kh: " + devSens.humi_k + + ", Zt: " + devSens.temp_z + + ", Zh: " + devSens.humi_z); if (len > 17) { - devcfs.mid = value.getUint16(13, false); - devcfs.vid = value.getUint16(15, false); - devcfs.i2c_addr = value.getUint8(17); + devSens.mid = value.getUint16(13, false); + devSens.vid = value.getUint16(15, false); + devSens.i2c_addr = value.getUint8(17); s = "???"; - if(devcfs.mid == 0x5959) - s = "CHT"+hex(devcfs.vid,4); - else if(devcfs.mid == 0 && devcfs.vid == 0xaaaa) + if(devSens.mid == 0x5959) + s = "CHT"+hex(devSens.vid,4); + else if(devSens.mid == 0 && devSens.vid == 0xaaaa) s = "AHT20"; - $('lblSensor').innerHTML = s+', I2C адрес: 0x'+hex(devcfs.i2c_addr,2); + $('lblSensor').innerHTML = s+', I2C адрес: 0x'+hex(devSens.i2c_addr,2); - addLog("Dev sensor # id: "+hex(devcfs.mid,4)+"-"+hex(devcfs.vid,4)+", i2c_addr: 0x"+hex(devcfs.i2c_addr,2)); + addLog("Dev sensor # id: "+hex(devSens.mid,4)+"-"+hex(devSens.vid,4)+", i2c_addr: 0x"+hex(devSens.i2c_addr,2)); } } else if((blkId == 0x55 || blkId == 0x56) && (len > 12)) { // CMD_ID_CFG Get/Set device config - devcfg.flg = value.getUint32(1, true); + devCfg.flg = value.getUint32(1, true); // bit0 - measure notify enable // bit1 - lcd chow time enable - devcfg.rf_tx_power = value.getUint8(5); - devcfg.advertising_interval = value.getUint8(6); - devcfg.connect_latency = value.getUint8(7); - devcfg.reserved1 = value.getUint8(8); - devcfg.measure_interval = value.getUint8(9); - devcfg.batt_interval = value.getUint8(10); - devcfg.averaging_measurements = value.getUint8(11); - devcfg.reserved2 = value.getUint8(12); - - $('inputFlag').value = devcfg.flg; // пока нет - $('inputTxPwr').value = devcfg.rf_tx_power; // 0..0x3f -> -20..+5 dBm нелинейное 0x1f = +0 дБм - $('inputLat').value = (devcfg.connect_latency + 1)*30; // = (connect_latency + 1)*30 ms - - $('inputAdvInt').value = devcfg.advertising_interval*62.5; // *62.5 = интервала рекламы в ms - $('inputMeasInt').value = devcfg.measure_interval; // *devcfg.advertising_interval*62.5 = опрос датчика в ms, value минимум = 2 (интервала рекламы) - $('inputAverInt').value = devcfg.averaging_measurements; // запись истории: при 0 - отключена, 1...255 * шаг опроса датчика = интерал записи истории - $('inputBatInt').value = devcfg.batt_interval; // в секундах, минимум 2 секунды, но кратно интервалу рекламы - - console.log("Dev config # flg: "+hex(devcfg.flg,8)+", tx: "+devcfg.rf_tx_power+", adi: "+devcfg.advertising_interval+", cly: "+devcfg.connect_latency+", msi: "+devcfg.measure_interval+", bti: "+devcfg.batt_interval+", avi: "+devcfg.averaging_measurements); + devCfg.tx_power = value.getUint8(5); + devCfg.advertising_interval = value.getUint8(6); + devCfg.connect_latency = value.getUint8(7); + devCfg.reserved1 = value.getUint8(8); + devCfg.measure_interval = value.getUint8(9); + devCfg.batt_interval = value.getUint8(10); + devCfg.averaging_measurements = value.getUint8(11); + devCfg.reserved2 = value.getUint8(12); + showConfig(); + console.log("Dev config # flg: " + hex(devCfg.flg,8) + + ", tx: " + devCfg.tx_power + + ", adi: " + devCfg.advertising_interval + + ", cli: " + devCfg.connect_latency + +" , msi: " + devCfg.measure_interval + + ", bti: " + devCfg.batt_interval + + ", avi: " + devCfg.averaging_measurements); addLog("Строка конфигурации: "+ bytesToHex(value.buffer)); } else if((blkId == 0xdb) && (len > 4)) { // CMD_ID_MEM_RW Read/Write memory len -= 4; let addr = value.getUint32(1,true); let s = bytesToHex(value.buffer.slice(5), len); - $('inputData').value = s; + $('inpData').value = s; addLog("Данные по адресу 0x" + hex(addr,8) + ': ' + s); showProgress("Считано: " + len + " байт из 0x" + hex(addr,8)); } else if((blkId == 0xda) && (len > 8)) { @@ -1184,57 +996,88 @@ function customBlkParse(value) { // $("cbind_key").value = '?'; } } else if(blkId == 0x23 && len >= 4) { - devtime.cur = value.getUint32(1,true); - console.log('Device Time: 0x' + hex(devtime.cur, 8)); - let dt = new Date(devtime.cur * 1000); + devTime.cur = value.getUint32(1,true); + console.log('Device Time: 0x' + hex(devTime.cur, 8)); + let dt = new Date(devTime.cur * 1000); let sDateTime = (dt.toISOString().slice(0, -5)).replace('T',' '); $('lblTime').innerHTML = sDateTime - addLog('Время на устройстве: ' + sDateTime); + let time = Date.now() / 1000 - (new Date()).getTimezoneOffset() * 60; + let delta = devTime.cur - time; + console.log('Время на устройстве: ' + sDateTime + ', Уход: ' + Math.round(delta) + ' сек'); if(len >= 8) { - devtime.set = value.getUint32(5,true); - console.log('Последняя установка времени: 0x' + hex(devtime.set,8)); - if(devtime.step == 1) { - if(devtime.set > 0x60000000) { - devtime.period = devtime.cur - devtime.set; - let time = Date.now() / 1000; - time -= (new Date()).getTimezoneOffset() * 60; - devtime.cmp = time; - let odt = new Date(devtime.set * 1000); + devTime.set = value.getUint32(5,true); + console.log('Последняя установка времени: 0x' + hex(devTime.set,8)); + if(devTime.step == 1) { + if(devTime.set > 0x60000000) { + devTime.period = devTime.cur - devTime.set; + devTime.cmp = time; + let odt = new Date(devTime.set * 1000); addLog('Последняя установка времени: '+(odt.toISOString().slice(0, -5)).replace('T',' ')); - console.log('Прошло на устройстве: ' + devtime.period.toFixed(1) + ' секунд'); - let rp = devtime.cmp - devtime.set; - let delta = rp - devtime.period; + console.log('Прошло на устройстве: ' + devTime.period.toFixed(1) + ' секунд'); + let rp = devTime.cmp - devTime.set; + let delta = rp - devTime.period; console.log('Прошло реально: ' + rp.toFixed(1) + ' секунд, разница: ' + delta.toFixed(1) + ' секунд'); if(rp >= 10800) { // 10800 - devtime.step = 2; + devTime.step = 2; console.log("Send cmd Get StepTimeSec..."); settingsCharacteristics.writeValue(new Uint8Array([0x24])).then(_ => { console.log('Get StepTimeSec...'); }); } else { - devtime.step = 0; + devTime.step = 0; addLog('Минимальный перод для расчета ухода часов 3 часа!'); } } else { - devtime.step = 0; + devTime.step = 0; addLog('Часы необходимо настроить заранее!'); } } } + } else if((blkId == 0x44) && (len > 8)) { + if(len != 9) { + devTrig.temp_min = value.getInt16(1, true); + devTrig.temp_max = value.getInt16(3, true); + devTrig.humi_min = value.getInt16(5, true); + devTrig.humi_max = value.getInt16(7, true); + if ( devSrv.services & SERVICE_SCREEN ) { + $("inpTempMin").value = (devTrig.temp_min/100.0).toFixed(2); + $("inpTempMax").value = (devTrig.temp_max/100.0).toFixed(2); + $("inpHumiMin").value = (devTrig.humi_min/100.0).toFixed(2); + $("inpHumiMax").value = (devTrig.humi_max/100.0).toFixed(2); + } + } + let x = 0; + if(len == 10) x = 1; + else if(len >= 18) x = 9; + if(x > 0) { + devTrig.temp_threshold = value.getInt16(x, true); + devTrig.humi_threshold = value.getInt16(x+2, true); + devTrig.temp_hysteresis = value.getInt16(x+4, true); + devTrig.humi_hysteresis = value.getInt16(x+6, true); + devTrig.flg = value.getUint8(x+8); + if( devSrv.services & SERVICE_TH_TRG ) { // + $("chkInvOut").checked = (devTrig.flg & 1) != 0; + $("inpTempThr").value = (devTrig.temp_threshold/100.0).toFixed(2); + $("inpHumiThr").value = (devTrig.humi_threshold/100.0).toFixed(2); + $("inpTempHsr").value = (devTrig.temp_hysteresis/100.0).toFixed(2); + $("inpHumiHsr").value = (devTrig.humi_hysteresis/100.0).toFixed(2); + } + } + addLog("Строка настроек триггера: "+ bytesToHex(value.buffer)); } else if(blkId == 0x20 && len >= 8) { devcmf.tmp_lo = value.getInt16(1, true); // temp lo devcmf.tmp_hi = value.getInt16(3, true); // temp hi devcmf.hm_lo = value.getUint16(5, true); // humi lo devcmf.hm_hi = value.getUint16(7, true); // humi lo - let s = 'Комфорт температура: '+(devcmf.tmp_lo/100.0).toFixed(2)+'..'+(devcmf.tmp_hi/100.0).toFixed(2)+'°C, Влажность: '+(devcmf.hm_lo/100.0).toFixed(2)+'..'+(devcmf.hm_hi/100.0).toFixed(2)+'%'; + let s = 'Комфорт Температура: ' + (devcmf.tmp_lo/100.0).toFixed(2) + '..' + (devcmf.tmp_hi/100.0).toFixed(2) + '°C' + + ', Влажность: ' + (devcmf.hm_lo/100.0).toFixed(2) + '..' + (devcmf.hm_hi/100.0).toFixed(2) + '%'; addLog(s); // UpdCmf(); } else if(blkId == 0x01 && len >= 1) { - devnm.name = new TextDecoder("utf-8").decode(value.buffer.slice(1)); - //if($("dev_name")) - // $("dev_name").value = dnm.name; - addLog("Имя устройства: '"+devnm.name+"'"); - } else if(blkId == 0x10 && len >= 7) { + devName = TextDecoder("utf-8").decode(value.buffer.slice(1)); + $("inpDevName").value = devName; + addLog("Имя устройства: '" + devName + "'"); + } else if(blkId == 0x10 && len > 6) { let mac = new Uint8Array(6); mac[0] = value.getUint8(6); mac[1] = value.getUint8(5); @@ -1242,7 +1085,9 @@ function customBlkParse(value) { mac[3] = value.getUint8(3); mac[4] = value.getUint8(2); mac[5] = value.getUint8(1); - addLog("MAC устройства: "+ bytesToHex(mac)); + let mac_txt = bytesToHex(mac); + $("inpDevMAC").value = mac_txt; + addLog("MAC устройства: "+ mac_txt); } else { console.log('blk: ' + dump8(value, value.byteLength)); addLog('Ответ на команду (' + hex(blkId,2) + '): ' + bytesToHex(value.buffer.slice(1))); @@ -1251,34 +1096,12 @@ function customBlkParse(value) { function getMemo(num) { if(cmdCharacteristic != null) { //addLog("getSensCfg..."); - flg_memo_act = true; - flg_memo_cnt = 0; + isMemoActive = true; + memoCount = 0; cmdCharacteristic.writeValue(new Uint8Array([0x35, num&0xff, (num>>8)&0xff])).catch(error => { console.log(error); addLog("getMemo() error!"); }); } } -function setDevTime() { - let time = Date.now()/1000; - time -= (new Date()).getTimezoneOffset() * 60; - blk = new Uint8Array(5); - blk[0] = 0x23; - blk[1] = time & 0xff; - blk[2] = (time >> 8) & 0xff; - blk[3] = (time >> 16) & 0xff; - blk[4] = (time >> 24) & 0xff; - addLog("Установка времени на утройстве ("+dump(blk, blk.length)+")..."); - cmdCharacteristic.writeValue(blk).then(_ => { - console.log('Send new DevTime ok'); - }); -} - -function getDevTime() { - if(cmdCharacteristic != null) { - // addLog("Получить время от устройства..."); - cmdCharacteristic.writeValue(new Uint8Array([0x23])).then(_ => { console.log('Send GetDevTime ok'); }); - } -} - function getDevCfg() { if(cmdCharacteristic != null) { //addLog("getDevCfg..."); @@ -1291,52 +1114,83 @@ function getSensCfg() { cmdCharacteristic.writeValue(new Uint8Array([0x25])).catch(error => { console.log(error); addLog("getSensCfg() error!"); }); } } + +function getDevTime() { + if(cmdCharacteristic != null) { + // addLog("Получить время от устройства..."); + cmdCharacteristic.writeValue(new Uint8Array([0x23])).then(_ => { console.log('Send GetDevTime ok'); }); + } +} + +function getDevMAC() { + if(cmdCharacteristic != null) { + cmdCharacteristic.writeValue(new Uint8Array([0x10])).catch(error => { console.log(error); addLog("getMAC() error!"); }); + } +} + +function getTrgCfg() { + if(cmdCharacteristic != null) { + cmdCharacteristic.writeValue(new Uint8Array([0x44])).catch(error => { console.log(error); addLog("getTrgCfg() error!"); }); + } +} + +function getDevName() { + if(cmdCharacteristic != null) { + cmdCharacteristic.writeValue(new Uint8Array([0x01])).catch(error => { console.log(error); addLog("getDevName() error!"); }); + } +} + function setDevCfg() { if(cmdCharacteristic != null) { // addLog("setDevCfg..."); - devcfg.flg = parseInt($('inputFlag').value)&0xffffffff; // пока нет - devcfg.rf_tx_power = parseInt($('inputTxPwr').value)&0x3f; // 0..0x3f -> -20..+5 dBm нелинейное 0x1f = +0 дБм - let connect_latency = parseInt($('inputLat').value); // = (connect_latency + 1)*30 ms + devCfg.flg = parseInt($('inpFlag').value)&0xffffffff; // пока не реализовано + devCfg.tx_power = parseInt($('inpTxPwr').value)&0x3f; // 0..0x3f -> -20..+5 dBm нелинейное 0x1f = +0 дБм + devCfg.flg = ($('chkCfgNotify').checked) ? 1 : 0; + devCfg.flg |= ($('chkCfgClock').checked) ? 2 : 0; + devCfg.flg |= ($('chkCfgSmiley').checked) ? 4 : 0; + devCfg.flg |= ($('chkCfgTrg').checked) ? 8 : 0; + devCfg.tx_power = $('selTxPwr').value & 0x3f; // 0..0x1f -> -20..+5 dBm ? нелинейное 0x1f = +0 дБм + let connect_latency = parseInt($('inpLat').value); // = (connect_latency + 1)*30 ms if (connect_latency < 0) { connect_latency = 0; } else if (connect_latency <= 7680) { connect_latency = (connect_latency/30) - 1; } else connect_latency = 255; - devcfg.connect_latency = connect_latency & 0xff; - let advertising_interval = parseInt($('inputAdvInt').value); + devCfg.connect_latency = connect_latency & 0xff; + let advertising_interval = parseInt($('inpAdvInt').value); if(advertising_interval <= 62.5) advertising_interval = 1; else if(advertising_interval >= 15937.5) advertising_interval = 255; else - advertising_interval = advertising_interval/62.5; - devcfg.advertising_interval = advertising_interval & 0xff; - devcfg.measure_interval = parseInt($('inputMeasInt').value); - if(devcfg.measure_interval < 2) // опрос датчика в интервалах рекламы, value минимум = 2 (интервала рекламы) - devcfg.measure_interval = 2; - else if(devcfg.measure_interval > 255) - devcfg.measure_interval = 255 - devcfg.averaging_measurements = parseInt($('inputAverInt').value); - if(devcfg.averaging_measurements < 0) // запись истории: при 0 - отключена, 1...255 * шаг опроса датчика = интерал записи истории - devcfg.averaging_measurements = 0; - if(devcfg.averaging_measurements > 255) - devcfg.averaging_measurements = 255; - devcfg.batt_interval = parseInt($('inputBatInt').value); - if(devcfg.batt_interval < 2) // в секундах, минимум 2 секунды, но кратно интервалу рекламы - devcfg.batt_interval = 2; - if(devcfg.batt_interval > 255) - devcfg.batt_interval = 255; + advertising_interval = advertising_interval / 62.5; + devCfg.advertising_interval = advertising_interval & 0xff; + devCfg.measure_interval = parseInt($('inpMeasInt').value); + if(devCfg.measure_interval < 2) // опрос датчика в интервалах рекламы, value минимум = 2 (интервала рекламы) + devCfg.measure_interval = 2; + else if(devCfg.measure_interval > 255) + devCfg.measure_interval = 255 + devCfg.averaging_measurements = parseInt($('inpAverInt').value); + if(devCfg.averaging_measurements < 0) // запись истории: при 0 - отключена, 1...255 * шаг опроса датчика = интерал записи истории + devCfg.averaging_measurements = 0; + if(devCfg.averaging_measurements > 255) + devCfg.averaging_measurements = 255; + devCfg.batt_interval = parseInt($('inpBatInt').value); + if(devCfg.batt_interval < 2) // в секундах, минимум 2 секунды, но кратно интервалу рекламы + devCfg.batt_interval = 2; + if(devCfg.batt_interval > 255) + devCfg.batt_interval = 255; blk = new Uint8Array([0x55, - devcfg.flg & 0xff, (devcfg.flg>>8) & 0xff, (devcfg.flg>>16) & 0xff, (devcfg.flg>>24) & 0xff, - devcfg.rf_tx_power, - devcfg.advertising_interval, - devcfg.connect_latency, - devcfg.reserved1, - devcfg.measure_interval, - devcfg.batt_interval, - devcfg.averaging_measurements, - devcfg.reserved2 + devCfg.flg & 0xff, (devCfg.flg>>8) & 0xff, (devCfg.flg>>16) & 0xff, (devCfg.flg>>24) & 0xff, + devCfg.tx_power, + devCfg.advertising_interval, + devCfg.connect_latency, + devCfg.reserved1, + devCfg.measure_interval, + devCfg.batt_interval, + devCfg.averaging_measurements, + devCfg.reserved2 ]); cmdCharacteristic.writeValue(blk).catch(error => { console.log(error); addLog("setDevCfg() error!");}); } @@ -1346,21 +1200,148 @@ function setSensCfg() { if(cmdCharacteristic != null) { addLog("setSensCfg..."); - devcfs.temp_k = parseInt($('inputTempK').value); - devcfs.humi_k = parseInt($('inputHumK').value); - devcfs.temp_z = parseInt($('inputTempZ').value); - devcfs.humi_z = parseInt($('inputHumZ').value); + devSens.temp_k = parseInt($('inpTempK').value); + devSens.humi_k = parseInt($('inpHumK').value); + devSens.temp_z = parseInt($('inpTempZ').value); + devSens.humi_z = parseInt($('inpHumZ').value); blk = new Uint8Array([0x55, - devcfs.temp_k & 0xff, (devcfs.temp_k >> 8) & 0xff, (devcfs.temp_k >> 16) & 0xff, (devcfs.temp_k >> 24) & 0xff, - devcfs.humi_k & 0xff, (devcfs.humi_k >> 8) & 0xff, (devcfs.humi_k >> 16) & 0xff, (devcfs.humi_k >> 24) & 0xff, - devcfs.temp_z & 0xff, (devcfs.temp_z >> 8) & 0xff, - devcfs.humi_z & 0xff, (devcfs.humi_z >> 8) & 0xff + devSens.temp_k & 0xff, (devSens.temp_k >> 8) & 0xff, (devSens.temp_k >> 16) & 0xff, (devSens.temp_k >> 24) & 0xff, + devSens.humi_k & 0xff, (devSens.humi_k >> 8) & 0xff, (devSens.humi_k >> 16) & 0xff, (devSens.humi_k >> 24) & 0xff, + devSens.temp_z & 0xff, (devSens.temp_z >> 8) & 0xff, + devSens.humi_z & 0xff, (devSens.humi_z >> 8) & 0xff ]); cmdCharacteristic.writeValue(new Uint8Array([0x25])).catch(error => { console.log(error); addLog("setSensCfg() error!"); }); } } +function setDevTime() { + let time = Date.now()/1000; + time -= (new Date()).getTimezoneOffset() * 60; + blk = new Uint8Array(5); + blk[0] = 0x23; + blk[1] = time & 0xff; + blk[2] = (time >> 8) & 0xff; + blk[3] = (time >> 16) & 0xff; + blk[4] = (time >> 24) & 0xff; + addLog("Установка времени на устройстве (" + dump(blk, blk.length) + ")..."); + cmdCharacteristic.writeValue(blk).then(_ => { + console.log('Время на устройстве синхронизировано'); + }); +} + +function setDevMAC() { + if(cmdCharacteristic != null) { + let el = $("inpDevMAC").value; + let len = el.length; + if(len == 12) { + let mac = hexToBytes(el); + len = mac.length; + if(len == 6 || len == 8) { + let blk = new Uint8Array(len+2); + blk[0] = 0x10; + blk[1] = mac[5]; + blk[2] = mac[4]; + blk[3] = mac[3]; + blk[4] = mac[2]; + blk[5] = mac[1]; + blk[6] = mac[0]; + if(len == 8) { + blk[7] = mac[7]; + blk[8] = mac[6]; + } + console.log("Send cmd New MAC ("+dump(blk, blk.length)+")..."); + cmdCharacteristic.writeValue(blk).then(_ => { + s = "Передача нового MAC: "+dump(mac, 6); + if(len == 8) + s += " RAND:" +dump(mac.slice(6), 2); + addLog(s+" ok"); + }); + return; + } + } + addLog("Строка MAC должна быть 6 байт в HEX виде!") + return; + } +} + +function setTrgCfg() { + if(cmdCharacteristic != null) { + let len = 0; + if((devSrv.services & 0x00000020) != 0) len = 8; + if((devSrv.services & 0x00004000) != 0) len += 9; + if(len > 0) { + let blk = new Uint8Array(len + 1); + blk[0] = 0x44; + let idx = 1; + if(len != 9) { + devTrig.temp_min = Math.round($("inpTempMin").value * 100); + devTrig.temp_max = Math.round($("inpTempMax").value * 100); + devTrig.humi_min = Math.round($("inpHumiMin").value * 100); + devTrig.humi_max = Math.round($("inpHumiMax").value * 100); + blk[1] = devTrig.temp_min & 0xff; + blk[2] = (devTrig.temp_min >> 8) & 0xff; + blk[3] = devTrig.temp_max & 0xff; + blk[4] = (devTrig.temp_max >> 8) & 0xff; + blk[5] = devTrig.humi_min & 0xff; + blk[6] = (devTrig.humi_min >> 8) & 0xff; + blk[7] = devTrig.humi_max & 0xff; + blk[8] = (devTrig.humi_max >> 8) & 0xff; + idx = 9; + } + if(len > 8) { + devTrig.temp_threshold = Math.round($("inpTempThr").value * 100); + devTrig.humi_threshold = Math.round($("inpHumiThr").value * 100); + devTrig.temp_hysteresis = Math.round($("inpTempHsr").value * 100); + devTrig.humi_hysteresis = Math.round($("inpHumiHsr").value * 100); + devTrig.flg = ($("chkInvOut").checked)? 1: 0; + blk[idx+0] = devTrig.temp_threshold & 0xff; + blk[idx+1] = (devTrig.temp_threshold >> 8) & 0xff; + blk[idx+2] = devTrig.humi_threshold & 0xff; + blk[idx+3] = (devTrig.humi_threshold >> 8) & 0xff; + blk[idx+4] = devTrig.temp_hysteresis & 0xff; + blk[idx+5] = (devTrig.temp_hysteresis >> 8) & 0xff; + blk[idx+6] = devTrig.humi_hysteresis & 0xff; + blk[idx+7] = (devTrig.humi_hysteresis >> 8) & 0xff; + blk[idx+8] = devTrig.flg & 0xff; + } + cmdCharacteristic.writeValue(blk).catch(error => { console.log(error); addLog("setTrgCfg() error!"); }); + } + } +} + +function setDevName(flg) { + if(cmdCharacteristic != null) { + if(flg) { + let eltxt = $("inpDevName").value; + let len = eltxt.length; + if(len > 1 && len < 20) { + let blk = new Uint8Array(1 + len); + blk[0] = 0x01; + blk.set((new TextEncoder()).encode(eltxt), 1); + console.log(blk); + cmdCharacteristic.writeValue(blk).catch(error => { console.log(error); addLog("setDevName() error!"); }); + } else + addLog("Имя устройства должно быть от 1 до 19 символов!"); + } else + cmdCharacteristic.writeValue(new Uint8Array([0x01, 0])).catch(error => { console.log(error); addLog("setDevName() error!"); }); + } +} + +function resetDevCfg() { + if(cmdCharacteristic != null) { + addLog("Restore connection default settings..."); + cmdCharacteristic.writeValue(new Uint8Array([0x56])).catch(error => { console.log(error); addLog("resetDevCfg() error!"); }); + } +} + +function resetSensCfg() { + if(cmdCharacteristic != null) { + addLog("Restore sensor default settings..."); + cmdCharacteristic.writeValue(new Uint8Array([0x26])).catch(error => { console.log(error); addLog("resetSensCfg() error!"); }); + } +} + function readFile(file) { var reader = new FileReader(); @@ -1427,13 +1408,13 @@ var getFile = new selectFile; getFile.targets('inpFile','lblFile'); function uploadFile() { - if ( isEmpty($("inputUrl").value) ) { + if ( isEmpty($("inpUrl").value) ) { getFile.simulate() } else { let regex = /[^\\]+$/; // match(regex); - //download(, $("inputUrl").value, "application/octet-stream"); - download($("inputUrl").value, "OTA.bin"); + //download(, $("inpUrl").value, "application/octet-stream"); + download($("inpUrl").value, "OTA.bin"); // readFile(file); } } @@ -1444,6 +1425,8 @@ function readFlash() { $('btnSaveFlash').disabled = false; } +var chart = null; + function openTab(evt, tabName) { var i, tabcontent, tablinks; tabcontent = document.getElementsByClassName("tabcontent"); @@ -1460,6 +1443,14 @@ function openTab(evt, tabName) { evt.currentTarget.className += " active"; // console.log(evt.currentTarget.className); } + if (tabName == 'tabCharts') { + if ((chartData.length > 0) && (chart != null)) + chart.updateOptions({'file': chartData}); + resizeChart(); + isChartEnabled = true; + } else { + isChartEnabled = false; + } } function selectConfigTab() { @@ -1468,44 +1459,186 @@ function selectConfigTab() { tablinks[0].className += " active"; } -window.onload = (event) => { - showState("Нет подключения" ); - selectConfigTab(); +// Буфер входящих данных +var chart = null; +var chartData = []; +var rslT = 1; +var rslH = 1; +var rangeT = [15, 25]; // y: { valueRange: [15, 25] }, +var rangeH = [25, 85]; // y2: { valueRange: [25, 85] } + +const arrayColumn = (arr, n) => arr.map(x => x[n]); + +function addChartData(temp, humi) { + + let dt = new Date(); + chartData.push([dt, temp, humi]); + if(chartData.length >= 1000) chartData.shift(); + + //console.log(arrayColumn(a, 1)); + //console.log(Math.min(...arrayColumn(a, 1))); + //console.log(Math.max(...arrayColumn(a, 1))); + let yMin = Math.min(...arrayColumn(chartData, 1)); + let yMax = Math.max(...arrayColumn(chartData, 1)); + let xMin = Math.floor(yMin / rslT); + let xMax = Math.floor(yMax / rslT) + 1; + rangeT = [xMin*rslT, xMax*rslT]; + + yMin = Math.min(...arrayColumn(chartData, 2)); + yMax = Math.max(...arrayColumn(chartData, 2)); + xMin = Math.floor(yMin / rslH); + xMax = Math.floor(yMax / rslH) + 1; + rangeH = [xMin*rslH, xMax*rslH]; + //console.log(rangeT, rangeH); + +/********************************* + if (chartData.length < 3) return; + if (chart == null) { + + chart = new Dygraph( + $("divChart"), + "ny-vs-sf.txt", + { + rollPeriod: 14, + showRoller: true, + customBars: true, + title: 'NYC vs. SF', + ylabel: 'Temperature (F)', + legend: 'always' + } + ); + *******************************/ +/****************************** + chart = new Dygraph( + $("divDygraph"), + "ny-vs-sf.txt", // chartData, + { + //title: "Live data", + //showRangeSelector: true, + //showRoller: true, + //rollPeriod: 1, + //xlabel: 'Time(sec)', + //ylabel: 'Temp(C°)', + //y2label: 'RH(%)', + colors: ['green', 'blue'], + series : { 'RH(%)': { axis: 'y2' } }, + labels: ['T(сек)', 'T(°C)', 'RH(%)'], + //divLabels: $('divLabels'), + legend: 'always', // "follow" + digitsAfterDecimal: 3, + rollPeriod: 14, + showRoller: true, + customBars: true, + title: 'NYC vs. SF', + ylabel: 'Temperature (F)', + legend: 'always' + } + ); + ******************************/ + //chart.updateOptions({'file': chartData}) +} + +function resizeChart() { + if (chart == null) return; + // console.log('resize однако'); + var element = $('divChart'); + var positionInfo = element.getBoundingClientRect(); + // console.log(positionInfo); + // var width = positionInfo.width; + // var height = positionInfo.height; + var width = $('tabCharts').clientWidth; + var height = $('tabCharts').clientHeight; + // console.log(width, height); + if (height < (320 + 100)) + height = 320; + else + height -= 100; + + if (width < (480 + 40)) + width = 480; + else + width -= 40; + chart.resize(width, height); + // console.log(width, height); } window.onload = function() { - document.querySelector("#inpFile").addEventListener("change", function() { - var file = this.files[0]; - if (file != null) { + document.querySelector("#inpFile").addEventListener("change", function() { + var file = this.files[0]; + if (file != null) { text = "не загружен"; - readFile(file); - } else { + readFile(file); + } else { $('lblFile').innerHTML = "не выбран"; + } + }, false); + + showState("Не подключено"); + + disableControls(true); + selectConfigTab(); + chart = new Dygraph( + $('divChart'), + [], + { + //title: "Live data", + //showRangeSelector: true, + //showRoller: true, + //rollPeriod: 1, + //xlabel: 'Time(sec)', + //ylabel: 'Temp(C°)', + //y2label: 'RH(%)', + /******************************/ + axes: { + y: { valueRange: rangeT }, + y2: { valueRange: rangeH } + }, + /******************************/ + colors: ['green', 'blue'], + series : { 'RH(%)': { axis: 'y2' } }, + labels: ['T(сек)', 'T(°C)', 'RH(%)'], + //labelsDiv: $('labelsDiv'), + legend: 'always', // "follow" + digitsAfterDecimal: 2, + } + ); + /****************************************** + // debug + // let a = [[1, 21, 81], [2, 20, 79], [3, 19, 83]]; + // addChartData(21, 81); + // addChartData(20, 79); + // addChartData(19, 83); + + for (i = 0; i < 128; i++) { + let t = 20 + (1 - Math.random() * 2); + let h = 70 + (5 - Math.random() * 10); + chartData.push([i, t, h]); + //addChartData(t, h); + } + chart.updateOptions( + { + file: chartData, + axes: { + y: { 'valueRange': rangeT }, + y2: { 'valueRange': rangeH } } - }, false); - - $("btnSaveFlash").onclick = function() { - if(flashBuffer != null) - download(flashBuffer, 'r11000000-00080000.bin', 'application/octet-stream;charset=utf-8'); - }; - - showState("Не подключено"); - - disableControls(true); + } + ); + // chart.updateOptions({'file': chartData}); + ******************************************/ + addEventListener("resize", resizeChart); };

PHY62x2 BTHome

-
+ -
- -


+
-
+ @@ -1518,38 +1651,70 @@ window.onload = function() { +
- +
- Параметры связи
- + + + + + + +
+ + + + + + +
КомфортТемпература от + до °C,Влажность от + до %RH
+ + + + + + + + + +
ТриггерТемпература: °C, Влажность: %RH, Гистерезис: °C %RH,
+ + +
+ + +
+Параметры связи - + - - - + + +
ФлагSend Notification Tx Power Conn. Latency
+ +
Интервалы @@ -1561,18 +1726,19 @@ window.onload = function() { Уровень батареи - - - - - - + + + + + + - + - - + + +
Параметры сенсора  @@ -1586,36 +1752,19 @@ window.onload = function() { - - + + - - + +
Поправочные коэффициенты
Коррекция смещения

- - -
-
- - + +
@@ -1624,17 +1773,16 @@ window.onload = function() { - +


- +
-
-

- - -

+

+
+ +

Memo Chart - График истории замеров

@@ -1646,9 +1794,9 @@ window.onload = function() { Данные (hex): - + - +
@@ -1659,9 +1807,30 @@ window.onload = function() { +
+ + + + + + + +
MAC Адрес:
+
+ + + + + + + + +
Имя устройства:

-
+ + +
diff --git a/bthome_phy6222/web/styles.css b/bthome_phy6222/web/styles.css new file mode 100644 index 0000000..234c67f --- /dev/null +++ b/bthome_phy6222/web/styles.css @@ -0,0 +1,316 @@ +/* basic sytles */ + +body { + font-family: Arial, 'Open Sans', sans-serif; + color: #204056; +} + +h1 { + font-size: 28px; + font-weight: 400; + text-align: center; + margin-top: 12px; + margin-bottom: 18px; +} + +hr { + height: 10px; + border: 0; + box-shadow: 0 10px 10px -10px #8c8b8b inset; +} + +span#info { + font-style: italic; +} + +.button, [type='button'] { + background-color: #1a73e8; + border: none; + border-radius: 4px; + color: white; + padding: 8px 24px; + text-align: center; + text-decoration: none; + display: inline-block; + font-size: 14px; + margin: 6px 6px; + cursor: pointer; + box-shadow: 0 3px 1px -2px #0003, 0 2px 2px #00000024, 0 1px 5px #0000001f; +} + +.button, [type='button'].ok { + background-color: #4CAF50; /* Green */ + color: white; + border-color: #4CAF50; +} + +.button, [type='button'].danger { + background-color: #f44336; /* Red */ + color: white; + border-color: #f44336; +} + +.button, [type='button']:disabled { + cursor: not-allowed; + opacity: 0.6; +} + +input[type="checkbox"] + label { + margin-right: 8px; +} + +div#div_v { + height:400px; + margin-top: 16px; + margin-bottom: 16px; +} + +div#labdiv { + margin-top: 16px; + margin-bottom: 16px; +} + +div#log { + padding: 12px; + font-style: italic; + font-size: 16px; +} + +div#MAC { + font-style: smaller; + margin: 8px; +} + +div#txtStatus { + font-style: italic; + font-size: 16px; + text-align: center; + background-color: #eef6fc; + padding-top: 5px; + padding-bottom: 5px; + margin-top: 8px; +} + +div#tempHumiData{ + text-align: center; + background-color: #eef6fc; + padding-top: 5px; + padding-bottom: 5px; +} + +input { + padding: 4px; + margin: 4px; +} + +select { + padding: 4px; +} + +/* menu */ + +.navbar { + width: 95%; + /* box-shadow: 0 1px 4px rgb(146 161 176 / 15%);*/ + position: absolute; + top: 0; +} + +.nav-container { + display: flex; + justify-content: space-between; + align-items: center; + height: 62px; +} + +.navbar .menu-items { + display: flex; +} + +.navbar .nav-container li { + list-style: none; +} + +.navbar .nav-container a { + font-size: 1.0rem; + font-weight: 400; +} + +.navbar .nav-container a:hover{ + font-weight: bolder; +} + +.nav-container { + display: block; + position: relative; + height: 60px; +} + +.nav-container .checkbox { + position: absolute; + display: block; + height: 32px; + width: 32px; + top: 20px; + left: 20px; + z-index: 5; + opacity: 0; + cursor: pointer; +} + +.nav-container .hamburger-lines { + display: block; + height: 26px; + width: 32px; + position: absolute; + top: 17px; + left: 20px; + z-index: 2; + display: flex; + flex-direction: column; + justify-content: space-between; +} + +.nav-container .hamburger-lines .line { + display: block; + height: 4px; + width: 100%; + border-radius: 10px; + background: #0e2431; +} + +.nav-container .hamburger-lines .line1 { + transform-origin: 0% 0%; + transition: transform 0.4s ease-in-out; +} + +.nav-container .hamburger-lines .line2 { + transition: transform 0.2s ease-in-out; +} + +.nav-container .hamburger-lines .line3 { + transform-origin: 0% 100%; + transition: transform 0.4s ease-in-out; +} + +.navbar .menu-items { + position: relative; + padding-top: 55px; + box-shadow: 5px 3px 13px 0px rgb(204 204 204 / 80%); + min-height: 100vh; + width: 60%; + transform: translate(-150%); + display: flex; + flex-direction: column; + transition: transform 0.5s ease-in-out; + text-align: center; + z-index: 1; + background: white; +} + +.navbar .menu-items li { + margin-bottom: 12px; + font-size: 1.2rem; + font-weight: 800; +} + + +.nav-container input[type="checkbox"]:checked ~ .menu-items { + transform: translateX(0); +} + +.nav-container input[type="checkbox"]:checked ~ .hamburger-lines .line1 { + transform: rotate(45deg); +} + +.nav-container input[type="checkbox"]:checked ~ .hamburger-lines .line2 { + transform: scaleY(0); +} + +.nav-container input[type="checkbox"]:checked ~ .hamburger-lines .line3 { + transform: rotate(-45deg); +} + +.nav-container input[type="checkbox"]:checked ~ .logo{ + display: none; +} + + +.shadowbox { + width: 15em; + border: 1px solid #333; + box-shadow: 8px 8px 5px #444; + padding: 8px 12px; + background-image: linear-gradient(180deg, #fff, #ddd 40%, #ccc); +} + +.shadowprogress { + width: 15em; + border: 1px solid #333; + /* box-shadow: 8px 8px 5px #444; */ + padding: 8px 12px; + /* background-image: linear-gradient(180deg, #fff, #ddd 40%, #ccc); */ +} + +.shadowerror { + color: red; + width: 15em; + border: 1px solid #333; + box-shadow: 8px 8px 5px #444; + padding: 8px 12px; + background-image: linear-gradient(180deg, #fff, #ddd 40%, #ccc); +} + + +/* Style the tab */ +.tab { + overflow: hidden; + border: 1px solid #ccc; + background-color: #f1f1f1; +} + +/* Style the buttons inside the tab */ +.tab button { + background-color: inherit; + float: left; + border: none; + border-radius: 4px; + outline: none; + cursor: pointer; + padding: 10px 20px; + transition: 0.3s; + font-size: 14px; +} + +/* Change background color of buttons on hover */ +.tab button:hover { + /* background-color: #ddd; */ + background-color: #1a73e8; +} + +/* Create an active/current tablink class */ +.tab button.active { + /* background-color: #ccc; */ + background-color: #4aa3ff; +} + +/* Style the tab content */ +.tabcontent { + display: none; + padding: 6px 12px; + border: 1px solid #ccc; + border-top: none; +} + +/* Create equal columns that floats next to each other */ +.column { + float: left; + padding: 5px; +} + +/* Clear floats after the columns */ +.row:after { + content: ""; + display: table; + clear: both; +}