Kaynağa Gözat

chore: remove unused files, add dev docker compose and dockerfile for live rebuilds and testing

master
Blaz Smehov 1 ay önce
ebeveyn
işleme
7e75e8309f
15 değiştirilmiş dosya ile 165 ekleme ve 2960 silme
  1. +0
    -320
      ResLevis-Diagram-2.0.drawio
  2. +0
    -354
      ResLevis-Diagram.drawio
  3. +0
    -44
      beacon.csv
  4. +0
    -0
      build/docker-compose.prod.yaml
  5. +158
    -0
      build/docker-compose.yml
  6. +7
    -0
      build/package/Dockerfile.dev
  7. +0
    -1
      cmd/bridge/main.go
  8. +0
    -17
      gateway.csv
  9. BIN
      presence.db
  10. +0
    -28
      presense.container
  11. +0
    -130
      test/README.md
  12. +0
    -560
      test/beacons_test.go
  13. +0
    -294
      test/distance_test.go
  14. +0
    -568
      test/mqtthandler_test.go
  15. +0
    -644
      test/typeMethods_test.go

+ 0
- 320
ResLevis-Diagram-2.0.drawio Dosyayı Görüntüle

@@ -1,320 +0,0 @@
<mxfile host="Electron" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/28.2.5 Chrome/138.0.7204.251 Electron/37.6.1 Safari/537.36" version="28.2.5">
<diagram name="Pagina-1" id="Fpn7J8KQqI0Mq3TqwKSc">
<mxGraphModel dx="1028" dy="611" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1169" pageHeight="827" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="CsGlrJDhfrg_2RR0sWjT-9" value="localization algorithm" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" vertex="1" parent="1">
<mxGeometry x="149" y="334" width="70" height="70" as="geometry" />
</mxCell>
<mxCell id="Xrb94kopVgDRZLuWBJpc-11" value="API Call From API Server Logic" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="302" y="439" width="190" height="30" as="geometry" />
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-1" value="API Server&lt;br&gt;(FastAPI)&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;+&lt;br&gt;&lt;br&gt;Logic Connector&lt;/div&gt;" style="whiteSpace=wrap;html=1;aspect=fixed;" parent="1" vertex="1">
<mxGeometry x="613" y="330" width="204" height="204" as="geometry" />
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-2" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.02;exitY=0.704;exitDx=0;exitDy=0;entryX=0;entryY=0.75;entryDx=0;entryDy=0;exitPerimeter=0;startArrow=none;startFill=0;endFill=1;" parent="1" source="krLT08NrNyH7hn3MfuL1-18" target="krLT08NrNyH7hn3MfuL1-19" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="680" y="240" as="sourcePoint" />
<mxPoint x="680" y="40" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-59" value="BLE" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="krLT08NrNyH7hn3MfuL1-2" vertex="1" connectable="0">
<mxGeometry x="0.1209" y="1" relative="1" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-3" value="BackEnd" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="508" y="770" width="60" height="30" as="geometry" />
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-4" value="FrontEnd" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="975" y="770" width="60" height="30" as="geometry" />
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-5" value="" style="endArrow=none;html=1;rounded=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;exitX=0;exitY=0;exitDx=50;exitDy=0;exitPerimeter=0;endFill=0;startArrow=classic;startFill=1;" parent="1" source="krLT08NrNyH7hn3MfuL1-24" target="krLT08NrNyH7hn3MfuL1-1" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="680" y="700" as="sourcePoint" />
<mxPoint x="585" y="595" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-7" value="WebGui&lt;br&gt;Config" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="950" y="312" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-8" value="WebGui&lt;br&gt;Status" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="950" y="399" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-9" value="WebGui&lt;br&gt;Alarm" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="950" y="585" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-10" value="WebGui&lt;br&gt;QueryLog" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="950" y="677" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-11" value="" style="endArrow=none;html=1;rounded=0;exitX=1;exitY=0;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;startArrow=classic;startFill=1;" parent="1" source="krLT08NrNyH7hn3MfuL1-1" target="krLT08NrNyH7hn3MfuL1-7" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="750" y="160" as="sourcePoint" />
<mxPoint x="800" y="110" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-12" value="" style="endArrow=none;html=1;rounded=0;exitX=1;exitY=0.25;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;startArrow=classic;startFill=1;" parent="1" source="krLT08NrNyH7hn3MfuL1-1" target="krLT08NrNyH7hn3MfuL1-8" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="840" y="428" as="sourcePoint" />
<mxPoint x="1060" y="280" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-13" value="" style="endArrow=none;html=1;rounded=0;exitX=1;exitY=0.75;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;startArrow=classic;startFill=1;" parent="1" source="krLT08NrNyH7hn3MfuL1-1" target="krLT08NrNyH7hn3MfuL1-9" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="840" y="395" as="sourcePoint" />
<mxPoint x="1060" y="360" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-14" value="" style="endArrow=none;html=1;rounded=0;exitX=1;exitY=1;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;startArrow=classic;startFill=1;" parent="1" source="krLT08NrNyH7hn3MfuL1-1" target="krLT08NrNyH7hn3MfuL1-10" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="820" y="450" as="sourcePoint" />
<mxPoint x="1020" y="531" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-15" value="WebGui&lt;br&gt;QueryStatus" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="950" y="487" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-16" value="" style="endArrow=none;html=1;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;startArrow=classic;startFill=1;" parent="1" source="krLT08NrNyH7hn3MfuL1-1" target="krLT08NrNyH7hn3MfuL1-15" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="850" y="309" as="sourcePoint" />
<mxPoint x="1050" y="300" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-17" value="Operator" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;outlineConnect=0;" parent="1" vertex="1">
<mxGeometry x="1114" y="560" width="30" height="60" as="geometry" />
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-18" value="Beacon" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" parent="1" vertex="1">
<mxGeometry x="1012" y="60" width="50" height="50" as="geometry" />
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-19" value="Gateway&amp;nbsp;" style="triangle;whiteSpace=wrap;html=1;direction=west;" parent="1" vertex="1">
<mxGeometry x="644" y="79" width="90" height="80" as="geometry" />
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-20" value="MQTT&lt;br&gt;Broker" style="shape=process;whiteSpace=wrap;html=1;backgroundOutline=1;" parent="1" vertex="1">
<mxGeometry x="86" y="79" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-21" value="LOG" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;" parent="1" vertex="1">
<mxGeometry x="115" y="613" width="60" height="80" as="geometry" />
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-23" value="&lt;br&gt;Tracker&lt;br&gt;LOCALIZATION&lt;br&gt;(state)" style="shape=internalStorage;whiteSpace=wrap;html=1;backgroundOutline=1;" parent="1" vertex="1">
<mxGeometry x="318" y="240" width="141" height="80" as="geometry" />
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-24" value="USER&lt;br&gt;CONFIG" style="shape=cube;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;darkOpacity=0.05;darkOpacity2=0.1;" parent="1" vertex="1">
<mxGeometry x="665" y="676" width="120" height="80" as="geometry" />
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-25" value="Beacon" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" parent="1" vertex="1">
<mxGeometry x="1012" y="120" width="50" height="50" as="geometry" />
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-26" value="Beacon" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" parent="1" vertex="1">
<mxGeometry x="1012" y="180" width="50" height="50" as="geometry" />
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-27" value="Beacon" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" parent="1" vertex="1">
<mxGeometry x="1012" y="240" width="50" height="50" as="geometry" />
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-28" value="Gateway&amp;nbsp;" style="triangle;whiteSpace=wrap;html=1;direction=west;" parent="1" vertex="1">
<mxGeometry x="650" y="193" width="87" height="80" as="geometry" />
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-29" value="" style="endArrow=classic;html=1;rounded=0;entryX=0;entryY=0.25;entryDx=0;entryDy=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;endFill=1;" parent="1" source="krLT08NrNyH7hn3MfuL1-25" target="krLT08NrNyH7hn3MfuL1-19" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="990" y="150" as="sourcePoint" />
<mxPoint x="170" y="120" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-60" value="BLE" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="krLT08NrNyH7hn3MfuL1-29" vertex="1" connectable="0">
<mxGeometry x="0.038" y="3" relative="1" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-30" value="" style="endArrow=classic;html=1;rounded=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.75;entryDx=0;entryDy=0;endFill=1;" parent="1" source="krLT08NrNyH7hn3MfuL1-26" target="krLT08NrNyH7hn3MfuL1-28" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="140" y="160" as="sourcePoint" />
<mxPoint x="220" y="165" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-61" value="BLE" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="krLT08NrNyH7hn3MfuL1-30" vertex="1" connectable="0">
<mxGeometry x="0.0103" y="2" relative="1" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-31" value="" style="endArrow=classic;html=1;rounded=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.25;entryDx=0;entryDy=0;endFill=1;" parent="1" source="krLT08NrNyH7hn3MfuL1-27" target="krLT08NrNyH7hn3MfuL1-28" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="130" y="180" as="sourcePoint" />
<mxPoint x="773" y="220" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-62" value="BLE" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="krLT08NrNyH7hn3MfuL1-31" vertex="1" connectable="0">
<mxGeometry x="0.0203" y="-2" relative="1" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-32" value="" style="endArrow=classic;html=1;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=1.017;entryY=0.38;entryDx=0;entryDy=0;entryPerimeter=0;endFill=1;" parent="1" source="krLT08NrNyH7hn3MfuL1-19" target="krLT08NrNyH7hn3MfuL1-20" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="210" y="160" as="sourcePoint" />
<mxPoint x="290" y="165" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-63" value="IP MQTT" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="krLT08NrNyH7hn3MfuL1-32" vertex="1" connectable="0">
<mxGeometry x="-0.2888" y="-1" relative="1" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-33" value="" style="endArrow=classic;html=1;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.75;entryDx=0;entryDy=0;endFill=1;" parent="1" source="krLT08NrNyH7hn3MfuL1-28" target="krLT08NrNyH7hn3MfuL1-20" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="280" y="190" as="sourcePoint" />
<mxPoint x="370" y="240" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-64" value="IP MQTT" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="krLT08NrNyH7hn3MfuL1-33" vertex="1" connectable="0">
<mxGeometry x="-0.2558" relative="1" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-34" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;startArrow=none;startFill=0;endFill=1;" parent="1" source="krLT08NrNyH7hn3MfuL1-46" target="krLT08NrNyH7hn3MfuL1-20" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="400" y="281" as="sourcePoint" />
<mxPoint x="400" y="190" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-36" value="&lt;br&gt;Tracker Sensors:&lt;br&gt;Alarm, Temp, &lt;br&gt;battery level&lt;br&gt;...&lt;br&gt;Heartbest&lt;br&gt;(state)" style="shape=internalStorage;whiteSpace=wrap;html=1;backgroundOutline=1;dx=20;dy=10;" parent="1" vertex="1">
<mxGeometry x="318" y="486" width="140" height="104" as="geometry" />
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-37" value="" style="endArrow=classic;html=1;rounded=0;exitX=1;exitY=0.358;exitDx=0;exitDy=0;startArrow=none;startFill=0;endFill=1;exitPerimeter=0;" parent="1" source="krLT08NrNyH7hn3MfuL1-46" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="196.3699999999999" y="466.95000000000005" as="sourcePoint" />
<mxPoint x="610" y="430" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-39" value="" style="endArrow=none;html=1;rounded=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;endFill=0;exitX=0.949;exitY=0.3;exitDx=0;exitDy=0;exitPerimeter=0;startArrow=classic;startFill=1;" parent="1" source="krLT08NrNyH7hn3MfuL1-46" target="krLT08NrNyH7hn3MfuL1-23" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="310" y="350" as="sourcePoint" />
<mxPoint x="385" y="640" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-40" value="" style="endArrow=classic;html=1;rounded=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;exitX=1;exitY=1;exitDx=0;exitDy=0;endFill=1;" parent="1" source="krLT08NrNyH7hn3MfuL1-46" target="krLT08NrNyH7hn3MfuL1-36" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="350" y="360" as="sourcePoint" />
<mxPoint x="350" y="460" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-41" value="" style="endArrow=classic;html=1;rounded=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryPerimeter=0;endFill=1;" parent="1" source="krLT08NrNyH7hn3MfuL1-46" target="krLT08NrNyH7hn3MfuL1-21" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="450" y="460" as="sourcePoint" />
<mxPoint x="360" y="560" as="targetPoint" />
<Array as="points" />
</mxGeometry>
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-46" value="Presense&lt;br&gt;CORE" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" parent="1" vertex="1">
<mxGeometry x="90" y="390" width="110" height="110" as="geometry" />
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-52" value="User" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;outlineConnect=0;" parent="1" vertex="1">
<mxGeometry x="1101" y="70" width="30" height="60" as="geometry" />
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-56" value="User" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;outlineConnect=0;" parent="1" vertex="1">
<mxGeometry x="1102" y="181" width="30" height="60" as="geometry" />
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-69" value="" style="endArrow=none;html=1;rounded=0;" parent="1" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="10" y="10" as="sourcePoint" />
<mxPoint x="1160" y="10" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-71" value="" style="endArrow=none;html=1;rounded=0;" parent="1" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="10" y="810" as="sourcePoint" />
<mxPoint x="1160" y="810" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-72" value="" style="endArrow=none;html=1;rounded=0;" parent="1" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="1160" y="810" as="sourcePoint" />
<mxPoint x="1160" y="10" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-73" value="" style="endArrow=none;html=1;rounded=0;" parent="1" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="10" y="10" as="sourcePoint" />
<mxPoint x="10" y="810" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-74" value="&lt;b&gt;&lt;font style=&quot;font-size: 27px;&quot;&gt;ResLevis DIAGRAM&lt;/font&gt;&lt;/b&gt;" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="398" y="30" width="370" height="30" as="geometry" />
</mxCell>
<mxCell id="krLT08NrNyH7hn3MfuL1-75" value="&lt;b style=&quot;&quot;&gt;&lt;font style=&quot;font-size: 13px;&quot;&gt;Ver 2.0 2025/10/09&lt;/font&gt;&lt;/b&gt;" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="410" y="49" width="370" height="30" as="geometry" />
</mxCell>
<mxCell id="uKMjvkko2EQDpS27TLVR-1" value="&lt;br&gt;GW &lt;br&gt;and Tracker definition&lt;br&gt;model&lt;br&gt;supported&lt;br&gt;Decode-lib&lt;br&gt;Raw-Data&lt;div&gt;&lt;br&gt;&lt;/div&gt;" style="shape=cube;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;darkOpacity=0.05;darkOpacity2=0.1;" parent="1" vertex="1">
<mxGeometry x="334" y="613" width="110" height="126" as="geometry" />
</mxCell>
<mxCell id="uKMjvkko2EQDpS27TLVR-4" value="topic&lt;br&gt;Subscribe&lt;br&gt;publish_out" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="72" y="331" width="76" height="47" as="geometry" />
</mxCell>
<mxCell id="uKMjvkko2EQDpS27TLVR-5" value="topic&lt;br&gt;Publish&lt;br&gt;publish_out" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="532" y="133" width="76" height="47" as="geometry" />
</mxCell>
<mxCell id="Xrb94kopVgDRZLuWBJpc-1" value="" style="endArrow=classic;html=1;rounded=0;endFill=1;exitX=0;exitY=0;exitDx=0;exitDy=53;exitPerimeter=0;entryX=0.687;entryY=0.962;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="uKMjvkko2EQDpS27TLVR-1" target="krLT08NrNyH7hn3MfuL1-46" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="416" y="695" as="sourcePoint" />
<mxPoint x="250" y="620" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="Xrb94kopVgDRZLuWBJpc-2" value="R/W" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="100" y="560" width="60" height="30" as="geometry" />
</mxCell>
<mxCell id="Xrb94kopVgDRZLuWBJpc-3" value="R/W" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="668" y="591" width="60" height="30" as="geometry" />
</mxCell>
<mxCell id="Xrb94kopVgDRZLuWBJpc-4" value="R" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="230" y="560" width="60" height="30" as="geometry" />
</mxCell>
<mxCell id="Xrb94kopVgDRZLuWBJpc-6" value="R/W" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="210" y="290" width="60" height="30" as="geometry" />
</mxCell>
<mxCell id="Xrb94kopVgDRZLuWBJpc-7" value="R/W" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="230" y="478" width="60" height="30" as="geometry" />
</mxCell>
<mxCell id="Xrb94kopVgDRZLuWBJpc-8" value="R" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="100" y="273" width="60" height="30" as="geometry" />
</mxCell>
<mxCell id="Xrb94kopVgDRZLuWBJpc-9" value="API Call From CORE" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="316" y="403" width="142" height="30" as="geometry" />
</mxCell>
<mxCell id="Xrb94kopVgDRZLuWBJpc-10" value="" style="endArrow=none;html=1;rounded=0;exitX=0.964;exitY=0.656;exitDx=0;exitDy=0;startArrow=classic;startFill=1;endFill=0;exitPerimeter=0;" parent="1" source="krLT08NrNyH7hn3MfuL1-46" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="206" y="463.5" as="sourcePoint" />
<mxPoint x="610" y="462" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="CsGlrJDhfrg_2RR0sWjT-2" value="" style="rounded=0;whiteSpace=wrap;html=1;opacity=40;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
<mxGeometry x="60" y="220" width="460" height="550" as="geometry" />
</mxCell>
<mxCell id="CsGlrJDhfrg_2RR0sWjT-3" value="&lt;b&gt;Developed by SenLab&lt;/b&gt;" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="68" y="734" width="84" height="30" as="geometry" />
</mxCell>
<mxCell id="CsGlrJDhfrg_2RR0sWjT-4" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;opacity=40;" vertex="1" parent="1">
<mxGeometry x="560" y="300" width="280" height="470" as="geometry" />
</mxCell>
<mxCell id="CsGlrJDhfrg_2RR0sWjT-5" value="&lt;b&gt;Developed by AFA Systems&lt;/b&gt;" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="568" y="734" width="84" height="30" as="geometry" />
</mxCell>
<mxCell id="CsGlrJDhfrg_2RR0sWjT-6" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;opacity=40;" vertex="1" parent="1">
<mxGeometry x="870" y="300" width="230" height="470" as="geometry" />
</mxCell>
<mxCell id="CsGlrJDhfrg_2RR0sWjT-7" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;opacity=40;" vertex="1" parent="1">
<mxGeometry x="560" y="300" width="310" height="100" as="geometry" />
</mxCell>
<mxCell id="CsGlrJDhfrg_2RR0sWjT-8" value="&lt;b&gt;Developed Maestry&amp;nbsp;&lt;/b&gt;" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="867" y="737" width="84" height="30" as="geometry" />
</mxCell>
<mxCell id="CsGlrJDhfrg_2RR0sWjT-11" value="" style="endArrow=classic;html=1;rounded=0;entryX=0;entryY=0.25;entryDx=0;entryDy=0;endFill=1;exitX=1;exitY=0;exitDx=0;exitDy=0;" edge="1" parent="1" source="CsGlrJDhfrg_2RR0sWjT-9" target="krLT08NrNyH7hn3MfuL1-23">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="350" y="513" as="sourcePoint" />
<mxPoint x="474" y="370" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="CsGlrJDhfrg_2RR0sWjT-13" value="R" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="235" y="342.5" width="60" height="30" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

+ 0
- 354
ResLevis-Diagram.drawio Dosyayı Görüntüle

@@ -1,354 +0,0 @@
<mxfile host="app.diagrams.net" agent="Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:144.0) Gecko/20100101 Firefox/144.0" version="28.2.7">
<diagram name="Pagina-1" id="Q54yxviGp2ibMrAPitEd">
<mxGraphModel dx="1426" dy="799" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="9h2_EveRN_1U4OZwnWZC-2" value="API Call From API Server Logic" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;rotation=0;" vertex="1" parent="1">
<mxGeometry x="360" y="460" width="190" height="30" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-3" value="API Server&lt;br&gt;(FastAPI)&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;+&lt;br&gt;&lt;br&gt;Logic Connector&lt;/div&gt;" style="whiteSpace=wrap;html=1;aspect=fixed;" vertex="1" parent="1">
<mxGeometry x="613" y="330" width="204" height="204" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-4" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.02;exitY=0.704;exitDx=0;exitDy=0;entryX=0;entryY=0.75;entryDx=0;entryDy=0;exitPerimeter=0;startArrow=none;startFill=0;endFill=1;" edge="1" parent="1" source="9h2_EveRN_1U4OZwnWZC-20" target="9h2_EveRN_1U4OZwnWZC-21">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="680" y="240" as="sourcePoint" />
<mxPoint x="680" y="40" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-5" value="BLE" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="9h2_EveRN_1U4OZwnWZC-4">
<mxGeometry x="0.1209" y="1" relative="1" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-6" value="BackEnd" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="508" y="770" width="60" height="30" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-7" value="FrontEnd" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="975" y="770" width="60" height="30" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-8" value="" style="endArrow=none;html=1;rounded=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;exitX=0;exitY=0;exitDx=50;exitDy=0;exitPerimeter=0;endFill=0;startArrow=classic;startFill=1;" edge="1" parent="1" source="9h2_EveRN_1U4OZwnWZC-25" target="9h2_EveRN_1U4OZwnWZC-3">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="680" y="700" as="sourcePoint" />
<mxPoint x="585" y="595" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-9" value="WebGui&lt;br&gt;Config" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="950" y="312" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-10" value="WebGui&lt;br&gt;Status" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="950" y="399" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-11" value="WebGui&lt;br&gt;Alarm" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="950" y="585" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-12" value="WebGui&lt;br&gt;QueryLog" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="950" y="677" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-13" value="" style="endArrow=none;html=1;rounded=0;exitX=1;exitY=0;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;startArrow=classic;startFill=1;" edge="1" parent="1" source="9h2_EveRN_1U4OZwnWZC-3" target="9h2_EveRN_1U4OZwnWZC-9">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="750" y="160" as="sourcePoint" />
<mxPoint x="800" y="110" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-14" value="" style="endArrow=none;html=1;rounded=0;exitX=1;exitY=0.25;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;startArrow=classic;startFill=1;" edge="1" parent="1" source="9h2_EveRN_1U4OZwnWZC-3" target="9h2_EveRN_1U4OZwnWZC-10">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="840" y="428" as="sourcePoint" />
<mxPoint x="1060" y="280" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-15" value="" style="endArrow=none;html=1;rounded=0;exitX=1;exitY=0.75;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;startArrow=classic;startFill=1;" edge="1" parent="1" source="9h2_EveRN_1U4OZwnWZC-3" target="9h2_EveRN_1U4OZwnWZC-11">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="840" y="395" as="sourcePoint" />
<mxPoint x="1060" y="360" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-16" value="" style="endArrow=none;html=1;rounded=0;exitX=1;exitY=1;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;startArrow=classic;startFill=1;" edge="1" parent="1" source="9h2_EveRN_1U4OZwnWZC-3" target="9h2_EveRN_1U4OZwnWZC-12">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="820" y="450" as="sourcePoint" />
<mxPoint x="1020" y="531" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-17" value="WebGui&lt;br&gt;QueryStatus" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="950" y="487" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-18" value="" style="endArrow=none;html=1;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;startArrow=classic;startFill=1;" edge="1" parent="1" source="9h2_EveRN_1U4OZwnWZC-3" target="9h2_EveRN_1U4OZwnWZC-17">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="850" y="309" as="sourcePoint" />
<mxPoint x="1050" y="300" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-19" value="Operator" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;outlineConnect=0;" vertex="1" parent="1">
<mxGeometry x="1114" y="560" width="30" height="60" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-20" value="Beacon" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" vertex="1" parent="1">
<mxGeometry x="1012" y="60" width="50" height="50" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-21" value="Gateway&amp;nbsp;" style="triangle;whiteSpace=wrap;html=1;direction=west;" vertex="1" parent="1">
<mxGeometry x="644" y="79" width="90" height="80" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-22" value="MQTT&lt;br&gt;Broker" style="shape=process;whiteSpace=wrap;html=1;backgroundOutline=1;" vertex="1" parent="1">
<mxGeometry x="62" y="79" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-23" value="LOG" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;" vertex="1" parent="1">
<mxGeometry x="91" y="610" width="60" height="80" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-24" value="&lt;br&gt;Tracker&lt;br&gt;LOCALIZATION&lt;br&gt;(state)" style="shape=internalStorage;whiteSpace=wrap;html=1;backgroundOutline=1;" vertex="1" parent="1">
<mxGeometry x="269" y="241" width="141" height="80" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-25" value="USER&lt;br&gt;CONFIG" style="shape=cube;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;darkOpacity=0.05;darkOpacity2=0.1;" vertex="1" parent="1">
<mxGeometry x="665" y="676" width="120" height="80" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-26" value="Beacon" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" vertex="1" parent="1">
<mxGeometry x="1012" y="120" width="50" height="50" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-27" value="Beacon" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" vertex="1" parent="1">
<mxGeometry x="1012" y="180" width="50" height="50" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-28" value="Beacon" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" vertex="1" parent="1">
<mxGeometry x="1012" y="240" width="50" height="50" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-29" value="Gateway&amp;nbsp;" style="triangle;whiteSpace=wrap;html=1;direction=west;" vertex="1" parent="1">
<mxGeometry x="650" y="193" width="87" height="80" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-30" value="" style="endArrow=classic;html=1;rounded=0;entryX=0;entryY=0.25;entryDx=0;entryDy=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;endFill=1;" edge="1" parent="1" source="9h2_EveRN_1U4OZwnWZC-26" target="9h2_EveRN_1U4OZwnWZC-21">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="990" y="150" as="sourcePoint" />
<mxPoint x="170" y="120" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-31" value="BLE" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="9h2_EveRN_1U4OZwnWZC-30">
<mxGeometry x="0.038" y="3" relative="1" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-32" value="" style="endArrow=classic;html=1;rounded=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.75;entryDx=0;entryDy=0;endFill=1;" edge="1" parent="1" source="9h2_EveRN_1U4OZwnWZC-27" target="9h2_EveRN_1U4OZwnWZC-29">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="140" y="160" as="sourcePoint" />
<mxPoint x="220" y="165" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-33" value="BLE" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="9h2_EveRN_1U4OZwnWZC-32">
<mxGeometry x="0.0103" y="2" relative="1" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-34" value="" style="endArrow=classic;html=1;rounded=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.25;entryDx=0;entryDy=0;endFill=1;" edge="1" parent="1" source="9h2_EveRN_1U4OZwnWZC-28" target="9h2_EveRN_1U4OZwnWZC-29">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="130" y="180" as="sourcePoint" />
<mxPoint x="773" y="220" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-35" value="BLE" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="9h2_EveRN_1U4OZwnWZC-34">
<mxGeometry x="0.0203" y="-2" relative="1" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-36" value="" style="endArrow=classic;html=1;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=1.017;entryY=0.38;entryDx=0;entryDy=0;entryPerimeter=0;endFill=1;" edge="1" parent="1" source="9h2_EveRN_1U4OZwnWZC-21" target="9h2_EveRN_1U4OZwnWZC-22">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="210" y="160" as="sourcePoint" />
<mxPoint x="290" y="165" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-37" value="IP MQTT" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="9h2_EveRN_1U4OZwnWZC-36">
<mxGeometry x="-0.2888" y="-1" relative="1" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-38" value="" style="endArrow=classic;html=1;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.75;entryDx=0;entryDy=0;endFill=1;" edge="1" parent="1" source="9h2_EveRN_1U4OZwnWZC-29" target="9h2_EveRN_1U4OZwnWZC-22">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="280" y="190" as="sourcePoint" />
<mxPoint x="370" y="240" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-39" value="IP MQTT" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="9h2_EveRN_1U4OZwnWZC-38">
<mxGeometry x="-0.2558" relative="1" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-40" value="" style="endArrow=classic;html=1;rounded=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;startArrow=none;startFill=0;endFill=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;" edge="1" parent="1" source="9h2_EveRN_1U4OZwnWZC-76" target="9h2_EveRN_1U4OZwnWZC-22">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="146" y="260" as="sourcePoint" />
<mxPoint x="400" y="190" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-41" value="&lt;br&gt;Tracker Sensors:&lt;br&gt;Alarm, Temp, &lt;br&gt;battery level&lt;br&gt;...&lt;br&gt;Heartbest&lt;br&gt;(state)" style="shape=internalStorage;whiteSpace=wrap;html=1;backgroundOutline=1;dx=20;dy=10;" vertex="1" parent="1">
<mxGeometry x="420" y="541" width="140" height="104" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-42" value="" style="endArrow=classic;html=1;rounded=0;exitX=1;exitY=0.25;exitDx=0;exitDy=0;startArrow=none;startFill=0;endFill=1;" edge="1" parent="1" source="9h2_EveRN_1U4OZwnWZC-84">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="200" y="429.3800000000001" as="sourcePoint" />
<mxPoint x="610" y="430" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-44" value="" style="endArrow=classic;html=1;rounded=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;endFill=1;exitX=1;exitY=1;exitDx=0;exitDy=0;" edge="1" parent="1" source="9h2_EveRN_1U4OZwnWZC-84" target="9h2_EveRN_1U4OZwnWZC-41">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="300" y="500" as="sourcePoint" />
<mxPoint x="350" y="460" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-45" value="" style="endArrow=classic;html=1;rounded=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryPerimeter=0;endFill=1;" edge="1" parent="1" source="9h2_EveRN_1U4OZwnWZC-76" target="9h2_EveRN_1U4OZwnWZC-23">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="145" y="500" as="sourcePoint" />
<mxPoint x="360" y="560" as="targetPoint" />
<Array as="points" />
</mxGeometry>
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-47" value="User" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;outlineConnect=0;" vertex="1" parent="1">
<mxGeometry x="1101" y="70" width="30" height="60" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-48" value="User" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;outlineConnect=0;" vertex="1" parent="1">
<mxGeometry x="1102" y="181" width="30" height="60" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-49" value="" style="endArrow=none;html=1;rounded=0;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="10" y="10" as="sourcePoint" />
<mxPoint x="1160" y="10" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-50" value="" style="endArrow=none;html=1;rounded=0;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="10" y="810" as="sourcePoint" />
<mxPoint x="1160" y="810" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-51" value="" style="endArrow=none;html=1;rounded=0;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="1160" y="810" as="sourcePoint" />
<mxPoint x="1160" y="10" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-52" value="" style="endArrow=none;html=1;rounded=0;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="10" y="10" as="sourcePoint" />
<mxPoint x="10" y="810" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-53" value="&lt;b&gt;&lt;font style=&quot;font-size: 27px;&quot;&gt;ResLevis DIAGRAM&lt;/font&gt;&lt;/b&gt;" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="398" y="30" width="370" height="30" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-54" value="&lt;b style=&quot;&quot;&gt;&lt;font style=&quot;font-size: 13px;&quot;&gt;Ver 2.0 2025/10/09&lt;/font&gt;&lt;/b&gt;" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="410" y="49" width="370" height="30" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-56" value="topic&lt;br&gt;Subscribe&lt;br&gt;publish_out" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="54" y="170" width="76" height="47" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-57" value="topic&lt;br&gt;Publish&lt;br&gt;publish_out" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="532" y="133" width="76" height="47" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-59" value="R" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="100" y="560" width="60" height="30" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-60" value="R/W" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="668" y="591" width="60" height="30" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-61" value="R" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="152" y="498" width="60" height="30" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-62" value="R/W" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="206" y="312" width="60" height="30" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-63" value="R" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="330" y="530" width="60" height="30" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-64" value="R" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="100" y="273" width="60" height="30" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-65" value="API Call From CORE" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="384" y="410" width="142" height="30" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-66" value="" style="endArrow=none;html=1;rounded=0;exitX=1.017;exitY=0.606;exitDx=0;exitDy=0;startArrow=classic;startFill=1;endFill=0;exitPerimeter=0;" edge="1" parent="1" source="9h2_EveRN_1U4OZwnWZC-84">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="196.03999999999996" y="462.1600000000001" as="sourcePoint" />
<mxPoint x="610" y="462" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-68" value="&lt;b&gt;Developed by SenLab&lt;/b&gt;" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="68" y="734" width="84" height="30" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-69" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;opacity=40;" vertex="1" parent="1">
<mxGeometry x="560" y="300" width="280" height="470" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-70" value="&lt;b&gt;Developed by AFA Systems&lt;/b&gt;" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="568" y="734" width="84" height="30" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-71" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;opacity=40;" vertex="1" parent="1">
<mxGeometry x="870" y="300" width="230" height="470" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-72" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;opacity=40;" vertex="1" parent="1">
<mxGeometry x="560" y="300" width="310" height="100" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-73" value="&lt;b&gt;Developed Maestry&amp;nbsp;&lt;/b&gt;" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="867" y="737" width="84" height="30" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-76" value="Kafka" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="72" y="420" width="98" height="70" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-77" value="Decoder" style="ellipse;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="18" y="350" width="68" height="30" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-78" value="" style="endArrow=classic;startArrow=classic;html=1;rounded=0;exitX=0.691;exitY=0.867;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="9h2_EveRN_1U4OZwnWZC-77" target="9h2_EveRN_1U4OZwnWZC-76">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="50" y="410" as="sourcePoint" />
<mxPoint x="100" y="360" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-79" value="R/W" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" vertex="1" parent="1">
<mxGeometry x="62" y="390" width="60" height="30" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-80" value="" style="endArrow=classic;startArrow=classic;html=1;rounded=0;entryX=0;entryY=1;entryDx=0;entryDy=0;exitX=1;exitY=0;exitDx=0;exitDy=0;" edge="1" parent="1" source="9h2_EveRN_1U4OZwnWZC-76" target="9h2_EveRN_1U4OZwnWZC-24">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="190" y="360" as="sourcePoint" />
<mxPoint x="250" y="310" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-84" value="" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="230" y="412.5" width="120" height="85" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-89" value="Scorpio broker" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" vertex="1" parent="1">
<mxGeometry x="260" y="440.5" width="60" height="30" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-91" value="" style="endArrow=classic;html=1;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="9h2_EveRN_1U4OZwnWZC-76" target="9h2_EveRN_1U4OZwnWZC-84">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="180" y="530" as="sourcePoint" />
<mxPoint x="230" y="480" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-92" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.518;entryY=0.008;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="9h2_EveRN_1U4OZwnWZC-84" target="9h2_EveRN_1U4OZwnWZC-98">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="300" y="640" as="sourcePoint" />
<mxPoint x="284.5" y="621" as="targetPoint" />
<Array as="points" />
</mxGeometry>
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-93" value="R" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="212" y="561" width="60" height="30" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-97" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;" edge="1" parent="1" source="9h2_EveRN_1U4OZwnWZC-89" target="9h2_EveRN_1U4OZwnWZC-89">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-98" value="&lt;br&gt;GW &lt;br&gt;and Tracker definition&lt;br&gt;model&lt;br&gt;supported&lt;br&gt;Decode-lib&lt;br&gt;Raw-Data&lt;div&gt;&lt;br&gt;&lt;/div&gt;" style="shape=cube;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;darkOpacity=0.05;darkOpacity2=0.1;" vertex="1" parent="1">
<mxGeometry x="235" y="621" width="110" height="126" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-99" value="Redis cache" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="254.5" y="342" width="90.5" height="50" as="geometry" />
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-101" value="" style="endArrow=classic;startArrow=classic;html=1;rounded=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.403;entryY=0.98;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="9h2_EveRN_1U4OZwnWZC-84" target="9h2_EveRN_1U4OZwnWZC-99">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="260" y="440" as="sourcePoint" />
<mxPoint x="310" y="390" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-102" value="" style="endArrow=classic;startArrow=classic;html=1;rounded=0;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="170" y="420" as="sourcePoint" />
<mxPoint x="260" y="390" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="9h2_EveRN_1U4OZwnWZC-104" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;opacity=40;" vertex="1" parent="1">
<mxGeometry x="18" y="230" width="392" height="540" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

+ 0
- 44
beacon.csv Dosyayı Görüntüle

@@ -1,44 +0,0 @@
Position;Floor;RoomName;X;Y;Z;BeaconName;MAC
A01;0;PT-MAGA;200;50;107;BC-01;C3:00:00:57:B9:ED
A02;0;PT-MAGA;600;200;107;BC-02;C3:00:00:57:B9:E9
A03;0;PT-MAGA;175;1535;107;BC-03;C3:00:00:57:B9:F0
A04;0;PT-MAGA;620;1535;107;BC-04;C3:00:00:57:B9:E4
A05;0;PT-MAGA;580;710;72;BC-05;C3:00:00:57:B9:FA
A06;0;PT-MENS;800;1065;142;BC-06;C3:00:00:57:B9:F9
A07;0;PT-MENS;2165;315;107;BC-07;C3:00:00:57:B9:F8
A08;0;PT-MENS;2195;50;142;BC-08;C3:00:00:57:B9:F7
A09;0;PT-MENS;2895;50;142;BC-09;C3:00:00:57:B9:EA
A10;0;PT-MENS;2710;250;78;BC-10;C3:00:00:57:B9:EB
A11;0;PT-AMMI;1585;870;141;BC-11;C3:00:00:57:B9:EE
A12;0;PT-AMMI;1585;1540;141;BC-12;C3:00:00:57:B9:E2
A13;0;PT-AMMI;2130;875;107;BC-13;C3:00:00:57:B9:E5
A14;0;PT-AMMI;2095;1540;141;BC-14;C3:00:00:57:B9:D5
A15;0;PT-AMMI;1875;1200;73;BC-15;C3:00:00:57:B9:EC
A16;0;PT-PROD;2180;875;107;BC-16;C3:00:00:57:B9:D8
A17;0;PT-PROD;2180;1540;141;BC-17;C3:00:00:57:B9:E1
A18;0;PT-PROD;2930;880;141;BC-18;C3:00:00:57:B9:F3
A19;0;PT-PROD;2895;1530;141;BC-19;C3:00:00:57:B9:E0
A20;0;PT-PROD;2650;1180;107;BC-20;C3:00:00:57:B9:EF
A21;1;P1-NETW;800;1050;107;BC-21;C3:00:00:57:B9:E6
A22;1;P1-NETW;850;1545;107;BC-22;C3:00:00:57:B9:D4
A23;1;P1-NETW;1425;1050;107;BC-23;C3:00:00:57:B9:E8
A24;1;P1-NETW;1400;1530;107;BC-24;C3:00:00:57:B9:F1
A25;1;P1-NETW;1195;1315;72;BC-25;C3:00:00:57:B9:E7
A26;1;P1-RIUNI;2190;50;107;BC-26;C3:00:00:57:B9:D6
A27;1;P1-RIUNI;2180;465;107;BC-27;C3:00:00:57:B9:D7
A28;1;P1-RIUNI;2890;50;107;BC-28;C3:00:00:57:B9:F6
A29;1;P1-RIUNI;2525;465;76;BC-29;C3:00:00:57:B9:F2
A30;1;P1-RIUNI;2540;280;69;BC-30;C3:00:00:57:B9:D3
A31;1;P1-SOFT;1895;865;107;BC-31;C3:00:00:57:B9:F4
A32;1;P1-SOFT;1900;1535;107;BC-32;C3:00:00:57:B9:D9
A33;1;P1-SOFT;2320;870;72;BC-33;C3:00:00:57:B9:F5
A34;1;P1-SOFT;2330;1530;107;BC-34;C3:00:00:57:B9:DA
A35;1;P1-SOFT;2065;1190;20;BC-35;C3:00:00:57:B9:DB
A36;1;P1-CUCO;2370;865;107;BC-36;C3:00:00:57:B9:DC
A37;1;P1-CUCO;2380;1535;93;BC-37;C3:00:00:57:B9:DD
A38;1;P1-CUCO;2940;870;93;BC-38;C3:00:00:57:B9:E3
A39;1;P1-CUCO;2905;1540;93;BC-39;C3:00:00:57:B9:DF
A40;1;P1-CUCO;2550;1360;72;BC-40;C3:00:00:57:B9:DE
A41;1;P1-AMOR;830;50;100;BC-41;C3:00:00:39:47:DF
A42;1;P1-DINO;1788;50;117;BC-42;C3:00:00:39:47:E2
A43;1;TESTER;1026;1050;122;BC-43;C3:00:00:39:47:C4

build/docker-compose.yaml → build/docker-compose.prod.yaml Dosyayı Görüntüle


+ 158
- 0
build/docker-compose.yml Dosyayı Görüntüle

@@ -0,0 +1,158 @@
version: "2"
services:
db:
image: postgres
container_name: db
restart: always
ports:
- "127.0.0.1:5432:5432"
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
start_period: 30s

kafdrop:
image: obsidiandynamics/kafdrop
restart: "no"
ports:
- "127.0.0.1:9000:9000"
environment:
KAFKA_BROKERCONNECT: "kafka:29092"
depends_on:
- "kafka"
kafka:
image: apache/kafka:3.9.0
restart: "no"
ports:
# - "127.0.0.1:2181:2181"
- "127.0.0.1:9092:9092"
- "127.0.0.1:9093:9093"
healthcheck: # <-- ADD THIS BLOCK
test: ["CMD-SHELL", "/opt/kafka/bin/kafka-topics.sh --bootstrap-server 127.0.0.1:9092 --list"]
interval: 10s
timeout: 5s
retries: 10
start_period: 20s
environment:
KAFKA_NODE_ID: 1
KAFKA_PROCESS_ROLES: broker,controller
KAFKA_LISTENERS: INTERNAL://:29092,EXTERNAL://:9092,CONTROLLER://127.0.0.1:9093
KAFKA_ADVERTISED_LISTENERS: INTERNAL://kafka:29092,EXTERNAL://localhost:9092
KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT
KAFKA_CONTROLLER_QUORUM_VOTERS: 1@127.0.0.1:9093
KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0
KAFKA_NUM_PARTITIONS: 3

kafka-init:
image: apache/kafka:3.9.0
command: [ "sh", "-c", "ls -l /tmp/create_topic.sh && /tmp/create_topic.sh" ]
depends_on:
kafka:
condition: service_healthy
volumes:
- ./init-scripts/create_topic.sh:/tmp/create_topic.sh
environment:
- TOPIC_NAMES=topic1,topic2,topic3
valkey:
image: valkey/valkey:9.0.0
container_name: valkey
ports:
- "127.0.0.1:6379:6379"

presense-decoder:
build:
context: ../
dockerfile: build/package/Dockerfile.dev
image: presense-decoder
container_name: presense-decoder
environment:
- KAFKA_URL=kafka:29092
depends_on:
kafka-init:
condition: service_completed_successfully
db:
condition: service_healthy
restart: always
volumes:
- ../:/app
command: air --build.cmd "go build -buildvcs=false -o /tmp/decoder ./cmd/decoder" --build.bin "/tmp/decoder"
presense-server:
build:
context: ../
dockerfile: build/package/Dockerfile.dev
image: presense-server
container_name: presense-server
environment:
- VALKEY_URL=valkey:6379
- KAFKA_URL=kafka:29092
- DBHost=db
- DBUser=postgres
- DBPass=postgres
- DBName=postgres
ports:
- "127.0.0.1:1902:1902"
depends_on:
valkey:
condition: service_started
kafka-init:
condition: service_completed_successfully
db:
condition: service_healthy
restart: always
volumes:
- ../:/app
command: air --build.cmd "go build -buildvcs=false -o /tmp/server ./cmd/server" --build.bin "/tmp/server"

presense-bridge:
build:
context: ../
dockerfile: build/package/Dockerfile.dev
image: presense-bridge
container_name: presense-bridge
environment:
- KAFKA_URL=kafka:29092
- MQTT_HOST=192.168.1.101
- MQTT_USERNAME=user
- MQTT_PASSWORD=pass
depends_on:
kafka-init:
condition: service_completed_successfully
db:
condition: service_healthy
restart: always
volumes:
- ../:/app
command: air --build.cmd "go build -buildvcs=false -o /tmp/bridge ./cmd/bridge" --build.bin "/tmp/bridge"

presense-location:
build:
context: ../
dockerfile: build/package/Dockerfile.dev
image: presense-location
container_name: presense-location
environment:
- KAFKA_URL=kafka:29092
depends_on:
kafka-init:
condition: service_completed_successfully
db:
condition: service_healthy
restart: always
volumes:
- ../:/app
command: air --build.cmd "go build -buildvcs=false -o /tmp/location ./cmd/location" --build.bin "/tmp/location"



+ 7
- 0
build/package/Dockerfile.dev Dosyayı Görüntüle

@@ -0,0 +1,7 @@
FROM golang:1.25.0
WORKDIR /app
RUN go install github.com/air-verse/air@latest
# Pre-download dependencies for the whole workspace
COPY go.mod go.sum ./
RUN go mod download
CMD ["air"]

+ 0
- 1
cmd/bridge/main.go Dosyayı Görüntüle

@@ -42,7 +42,6 @@ func mqtthandler(writer *kafka.Writer, topic string, message []byte, appState *a
}
// fmt.Printf("reading: %+v\n", reading)
if !appState.BeaconExists(reading.MAC) {
fmt.Printf("Not tracking beacon: %s\n", reading.MAC)
continue
}



+ 0
- 17
gateway.csv Dosyayı Görüntüle

@@ -1,17 +0,0 @@
Position;Floor;RoomName;X;Y;Z;GatewayName;MAC
C01;0;PT-MAGA;220;250;13;GU-01;ac:23:3f:c1:dd:3c
C02;0;PT-FORM;825;745;13;GU-02;ac:23:3f:c1:dd:49
C03;0;PT-LVNS;825;1435;13;GU-03;ac:23:3f:c1:dc:ee
C04;0;PT-RECE;2010;620;13;GU-04;ac:23:3f:c1:dd:40
C05;0;PT-AMMI;1785;1260;13;GU-05;ac:23:3f:c1:dd:51
C06;0;PT-PROD;2720;1220;13;GU-06;ac:23:3f:c1:dd:48
C07;0;PT-BATH;2800;655;13;GU-07;ac:23:3f:c1:dd:50
C08;0;PT-MENS;2580;490;13;GU-08;ac:23:3f:c1:dc:d3
C09;1;P1-AMOR;900;50;13;GU-09;ac:23:3f:c1:dd:55
C10;1;P1-NETW;1310;1440;13;GU-10;ac:23:3f:c1:dc:d1
C11;1;P1-DINO;1662;480;13;GU-11;ac:23:3f:c1:dc:cb
C12;1;P1-COMM;1575;1455;13;GU-12;ac:23:3f:c1:dc:d2
C13;1;P1-SOFT;2290;965;13;GU-13;ac:23:3f:c1:dd:31
C14;1;P1-CUCO;2860;1120;13;GU-14;ac:23:3f:c1:dd:4b
C15;1;P1-BATH;2740;710;13;GU-15;ac:23:3f:c1:dd:4e
C16;1;P1-RIUN;2180;355;13;GU-16;ac:23:3f:c1:dc:cd

BIN
presence.db Dosyayı Görüntüle


+ 0
- 28
presense.container Dosyayı Görüntüle

@@ -1,28 +0,0 @@
[Unit]
Description=Presense
PartOf=podman.service
Wants=network-online.target podman-conf-login.service
After=podman.service network-online.target podman-conf-login.service
StartLimitIntervalSec=0

[Container]
Image=presense-go:latest
ContainerName=presense
PodmanArgs=-a stdout -a stderr
Network=sandbox.network
PublishPort=127.0.0.1:1902:8080
Environment=HTTP_HOST_PATH=0.0.0.0:8080
Environment=HTTPWS_HOST_PATH=0.0.0.0:8088
Environment=MQTT_HOST=emqx:1883
Environment=MQTT_USERNAME=sandbox
Environment=MQTT_PASSWORD=sandbox2025
Environment=MQTT_CLIENT_ID=presence-detector
Environment=DB_PATH=.presence.db

[Service]
Restart=always
TimeoutStartSec=infinity
RestartSec=5

[Install]
WantedBy=multi-user.target podman.service

+ 0
- 130
test/README.md Dosyayı Görüntüle

@@ -1,130 +0,0 @@
# Unit Tests Documentation

This directory contains comprehensive unit tests for the high-priority internal packages of the AFASystems presence detection system.

## Test Coverage

The following files have been thoroughly tested:

1. **`distance_test.go`** - Tests for distance calculation utilities
- `CalculateDistance()` - Distance calculation from RSSI and TX power
- `twosComp()` - Two's complement hex conversion
- `ValidateRSSI()` - RSSI value validation
- `ValidateTXPower()` - TX power validation
- Edge cases and real-world scenarios

2. **`beacons_test.go`** - Tests for beacon parsing utilities
- `ParseADFast()` - Advertising Data structure parsing
- `RemoveFlagBytes()` - Bluetooth flag bytes removal
- `LoopADStructures()` - Beacon type detection and parsing
- `isValidADStructure()` - AD structure validation
- Beacon format support: Ingics, Eddystone TLM, Minew B7

3. **`typeMethods_test.go`** - Tests for model type methods
- `Hash()` - Beacon event hash generation with battery rounding
- `ToJSON()` - JSON marshaling for beacon events
- `convertStructToMap()` - Generic struct-to-map conversion
- `RedisHashable()` - Redis hash map conversion for HTTPLocation and BeaconEvent
- JSON roundtrip integrity tests

4. **`mqtthandler_test.go`** - Tests for MQTT message processing
- `MqttHandler()` - Main MQTT message processing with JSON/CSV input
- `parseButtonState()` - Button counter parsing for different beacon formats
- Kafka writer integration (with mock)
- Hostname extraction from MQTT topics
- Error handling and edge cases

## Running Tests

### Run All Tests
```bash
go test ./test/... -v
```

### Run Specific Test File
```bash
go test ./test/distance_test.go -v
go test ./test/beacons_test.go -v
go test ./test/typeMethods_test.go -v
go test ./test/mqtthandler_test.go -v
```

### Run Tests for Specific Function
```bash
go test ./test/distance_test.go -run TestCalculateDistance -v
go test ./test/beacons_test.go -run TestParseADFast -v
go test ./test/typeMethods_test.go -run TestHash -v
go test ./test/mqtthandler_test.go -run TestMqttHandlerJSONArrayInput -v
```

### Run Benchmarks
```bash
# Run all benchmarks
go test ./test/... -bench=.

# Run specific benchmarks
go test ./test/distance_test.go -bench=BenchmarkCalculateDistance -v
go test ./test/beacons_test.go -bench=BenchmarkParseADFast -v
go test ./test/typeMethods_test.go -bench=BenchmarkHash -v
go test ./test/mqtthandler_test.go -bench=BenchmarkMqttHandlerJSON -v
```

### Run Tests with Coverage Report
```bash
go test ./test/... -cover
go test ./test/... -coverprofile=coverage.out
go tool cover -html=coverage.out -o coverage.html
```

### Run Tests with Race Detection
```bash
go test ./test/... -race -v
```

## Test Organization

Each test file follows Go testing conventions with:
- **Function tests**: Individual function behavior testing
- **Edge case tests**: Boundary conditions and error scenarios
- **Integration tests**: Multi-function workflow testing
- **Benchmark tests**: Performance measurement
- **Table-driven tests**: Multiple test cases with expected results

## Mock Objects

The mqtthandler tests use a `MockKafkaWriter` to simulate Kafka operations without requiring a running Kafka instance. This allows for:

- Deterministic test results
- Failure scenario simulation
- Message content verification
- Performance benchmarking

## Known Limitations

- **CSV Processing**: The original CSV handler in `mqtthandler.go` contains `os.Exit(2)` calls which make it untestable. The test demonstrates the intended structure but cannot fully validate CSV processing due to this design choice.
- **External Dependencies**: Tests use mocks for external systems (Kafka) to ensure tests remain fast and reliable.

## Best Practices Demonstrated

These tests demonstrate several Go testing best practices:

1. **Table-driven tests** for multiple scenarios
2. **Subtests** for logical test grouping
3. **Benchmark tests** for performance measurement
4. **Mock objects** for dependency isolation
5. **Error case testing** for robustness validation
6. **Deterministic testing** with consistent setup and teardown

## Running Tests in CI/CD

For automated testing environments:

```bash
# Standard CI test run
go test ./test/... -race -cover -timeout=30s

# Performance regression testing
go test ./test/... -bench=. -benchmem
```

This comprehensive test suite ensures the reliability and correctness of the core business logic in the AFASystems presence detection system.

+ 0
- 560
test/beacons_test.go Dosyayı Görüntüle

@@ -1,560 +0,0 @@
package utils

import (
"testing"

"github.com/AFASystems/presence/internal/pkg/model"
)

func TestParseADFast(t *testing.T) {
tests := []struct {
name string
input []byte
expected [][2]int
}{
{
name: "Empty input",
input: []byte{},
expected: [][2]int{},
},
{
name: "Single AD structure",
input: []byte{0x02, 0x01, 0x06},
expected: [][2]int{{0, 2}},
},
{
name: "Multiple AD structures",
input: []byte{0x02, 0x01, 0x06, 0x03, 0x02, 0x01, 0x02},
expected: [][2]int{{0, 2}, {3, 6}},
},
{
name: "Complex AD structures",
input: []byte{0x02, 0x01, 0x06, 0x1A, 0xFF, 0x4C, 0x00, 0x02, 0x15, 0xE2, 0xC5, 0x6D, 0xB5, 0xDF, 0xFB, 0x48, 0xD2, 0xB0, 0x60, 0xD0, 0xF5, 0xA7, 0x10, 0x96, 0xE0, 0x00, 0x00, 0x00, 0x00, 0xC5},
expected: [][2]int{{0, 2}, {2, 28}},
},
{
name: "Zero length AD structure",
input: []byte{0x00, 0x01, 0x06, 0x03, 0x02, 0x01, 0x02},
expected: [][2]int{{2, 5}},
},
{
name: "AD structure exceeding bounds",
input: []byte{0x05, 0x01, 0x06},
expected: [][2]int{},
},
{
name: "Incomplete AD structure",
input: []byte{0x03, 0x01},
expected: [][2]int{},
},
{
name: "Valid then invalid structure",
input: []byte{0x02, 0x01, 0x06, 0xFF, 0x01, 0x06},
expected: [][2]int{{0, 2}},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := ParseADFast(tt.input)
if len(result) != len(tt.expected) {
t.Errorf("ParseADFast() length = %v, expected %v", len(result), len(tt.expected))
return
}

for i, r := range result {
if r[0] != tt.expected[i][0] || r[1] != tt.expected[i][1] {
t.Errorf("ParseADFast()[%d] = %v, expected %v", i, r, tt.expected[i])
}
}
})
}
}

func TestRemoveFlagBytes(t *testing.T) {
tests := []struct {
name string
input []byte
expected []byte
}{
{
name: "Empty input",
input: []byte{},
expected: []byte{},
},
{
name: "Single byte input",
input: []byte{0x01},
expected: []byte{0x01},
},
{
name: "No flag bytes",
input: []byte{0x02, 0x01, 0x06, 0x03, 0x02, 0x01},
expected: []byte{0x02, 0x01, 0x06, 0x03, 0x02, 0x01},
},
{
name: "With flag bytes",
input: []byte{0x02, 0x01, 0x06, 0x1A, 0xFF, 0x4C, 0x00, 0x02},
expected: []byte{0x1A, 0xFF, 0x4C, 0x00, 0x02},
},
{
name: "Flag type is 0x01",
input: []byte{0x02, 0x01, 0x06, 0x05, 0x01, 0x02, 0x03, 0x04},
expected: []byte{0x05, 0x01, 0x02, 0x03, 0x04},
},
{
name: "Flag type is not 0x01",
input: []byte{0x02, 0x02, 0x06, 0x05, 0x01, 0x02, 0x03, 0x04},
expected: []byte{0x02, 0x02, 0x06, 0x05, 0x01, 0x02, 0x03, 0x04},
},
{
name: "Length exceeds bounds",
input: []byte{0xFF, 0x01, 0x06},
expected: []byte{0xFF, 0x01, 0x06},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := RemoveFlagBytes(tt.input)
if len(result) != len(tt.expected) {
t.Errorf("RemoveFlagBytes() length = %v, expected %v", len(result), len(tt.expected))
return
}

for i, b := range result {
if b != tt.expected[i] {
t.Errorf("RemoveFlagBytes()[%d] = %v, expected %v", i, b, tt.expected[i])
}
}
})
}
}

func TestIsValidADStructure(t *testing.T) {
tests := []struct {
name string
data []byte
expected bool
}{
{
name: "Empty data",
data: []byte{},
expected: false,
},
{
name: "Single byte",
data: []byte{0x01},
expected: false,
},
{
name: "Valid minimal structure",
data: []byte{0x01, 0x01},
expected: true,
},
{
name: "Valid structure",
data: []byte{0x02, 0x01, 0x06},
expected: true,
},
{
name: "Zero length",
data: []byte{0x00, 0x01, 0x06},
expected: false,
},
{
name: "Length exceeds data",
data: []byte{0x05, 0x01, 0x06},
expected: false,
},
{
name: "Length exactly matches",
data: []byte{0x02, 0x01, 0x06},
expected: true,
},
{
name: "Large valid structure",
data: []byte{0x1F, 0xFF, 0x4C, 0x00, 0x02, 0x15, 0xE2, 0xC5, 0x6D, 0xB5, 0xDF, 0xFB, 0x48, 0xD2, 0xB0, 0x60, 0xD0, 0xF5, 0xA7, 0x10, 0x96, 0xE0, 0x00, 0x00, 0x00, 0x00, 0xC5, 0x01, 0x02, 0x03, 0x04, 0x05},
expected: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := isValidADStructure(tt.data)
if result != tt.expected {
t.Errorf("isValidADStructure() = %v, expected %v", result, tt.expected)
}
})
}
}

func TestCheckIngics(t *testing.T) {
tests := []struct {
name string
ad []byte
expected bool
}{
{
name: "Valid Ingics beacon",
ad: []byte{0x08, 0xFF, 0x59, 0x00, 0x80, 0xBC, 0x12, 0x34, 0x01},
expected: true,
},
{
name: "Invalid - too short",
ad: []byte{0x05, 0xFF, 0x59, 0x00},
expected: false,
},
{
name: "Invalid - wrong manufacturer ID",
ad: []byte{0x08, 0xFF, 0x59, 0x01, 0x80, 0xBC, 0x12, 0x34, 0x01},
expected: false,
},
{
name: "Invalid - wrong type",
ad: []byte{0x08, 0xFE, 0x59, 0x00, 0x80, 0xBC, 0x12, 0x34, 0x01},
expected: false,
},
{
name: "Valid with minimum length",
ad: []byte{0x06, 0xFF, 0x59, 0x00, 0x80, 0xBC},
expected: true,
},
{
name: "Empty data",
ad: []byte{},
expected: false,
},
{
name: "Partial match only",
ad: []byte{0x06, 0xFF, 0x59, 0x00, 0x80},
expected: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := checkIngics(tt.ad)
if result != tt.expected {
t.Errorf("checkIngics() = %v, expected %v", result, tt.expected)
}
})
}
}

func TestParseIngicsState(t *testing.T) {
tests := []struct {
name string
ad []byte
expected model.BeaconEvent
}{
{
name: "Valid Ingics data",
ad: []byte{0x08, 0xFF, 0x59, 0x00, 0x80, 0xBC, 0x34, 0x12, 0x05},
expected: model.BeaconEvent{
Battery: 0x1234, // 4660 in little endian
Event: 0x05,
Type: "Ingics",
},
},
{
name: "Zero battery",
ad: []byte{0x08, 0xFF, 0x59, 0x00, 0x80, 0xBC, 0x00, 0x00, 0x00},
expected: model.BeaconEvent{
Battery: 0,
Event: 0,
Type: "Ingics",
},
},
{
name: "Max battery value",
ad: []byte{0x08, 0xFF, 0x59, 0x00, 0x80, 0xBC, 0xFF, 0xFF, 0xFF},
expected: model.BeaconEvent{
Battery: 0xFFFF,
Event: 0xFF,
Type: "Ingics",
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := parseIngicsState(tt.ad)
if result.Battery != tt.expected.Battery {
t.Errorf("parseIngicsState() Battery = %v, expected %v", result.Battery, tt.expected.Battery)
}
if result.Event != tt.expected.Event {
t.Errorf("parseIngicsState() Event = %v, expected %v", result.Event, tt.expected.Event)
}
if result.Type != tt.expected.Type {
t.Errorf("parseIngicsState() Type = %v, expected %v", result.Type, tt.expected.Type)
}
})
}
}

func TestCheckEddystoneTLM(t *testing.T) {
tests := []struct {
name string
ad []byte
expected bool
}{
{
name: "Valid Eddystone TLM",
ad: []byte{0x12, 0x16, 0xAA, 0xFE, 0x20, 0x00, 0x01, 0x02, 0x03, 0x04},
expected: true,
},
{
name: "Invalid - too short",
ad: []byte{0x03, 0x16, 0xAA},
expected: false,
},
{
name: "Invalid - wrong type",
ad: []byte{0x12, 0x15, 0xAA, 0xFE, 0x20, 0x00, 0x01, 0x02, 0x03, 0x04},
expected: false,
},
{
name: "Invalid - wrong company ID",
ad: []byte{0x12, 0x16, 0xAA, 0xFF, 0x20, 0x00, 0x01, 0x02, 0x03, 0x04},
expected: false,
},
{
name: "Invalid - wrong TLM type",
ad: []byte{0x12, 0x16, 0xAA, 0xFE, 0x21, 0x00, 0x01, 0x02, 0x03, 0x04},
expected: false,
},
{
name: "Valid with minimum length",
ad: []byte{0x04, 0x16, 0xAA, 0xFE, 0x20},
expected: true,
},
{
name: "Empty data",
ad: []byte{},
expected: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := checkEddystoneTLM(tt.ad)
if result != tt.expected {
t.Errorf("checkEddystoneTLM() = %v, expected %v", result, tt.expected)
}
})
}
}

func TestParseEddystoneState(t *testing.T) {
tests := []struct {
name string
ad []byte
expected model.BeaconEvent
}{
{
name: "Valid Eddystone TLM data",
ad: []byte{0x12, 0x16, 0xAA, 0xFE, 0x20, 0x34, 0x12, 0x78, 0x56, 0x00},
expected: model.BeaconEvent{
Battery: 0x1234, // 4660 in big endian (note: different from Ingics)
Type: "Eddystone",
},
},
{
name: "Zero battery",
ad: []byte{0x12, 0x16, 0xAA, 0xFE, 0x20, 0x00, 0x00, 0x78, 0x56, 0x00},
expected: model.BeaconEvent{
Battery: 0,
Type: "Eddystone",
},
},
{
name: "Max battery value",
ad: []byte{0x12, 0x16, 0xAA, 0xFE, 0x20, 0xFF, 0xFF, 0x78, 0x56, 0x00},
expected: model.BeaconEvent{
Battery: 0xFFFF,
Type: "Eddystone",
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := parseEddystoneState(tt.ad)
if result.Battery != tt.expected.Battery {
t.Errorf("parseEddystoneState() Battery = %v, expected %v", result.Battery, tt.expected.Battery)
}
if result.Type != tt.expected.Type {
t.Errorf("parseEddystoneState() Type = %v, expected %v", result.Type, tt.expected.Type)
}
})
}
}

func TestCheckMinewB7(t *testing.T) {
tests := []struct {
name string
ad []byte
expected bool
}{
{
name: "Valid Minew B7",
ad: []byte{0x08, 0x16, 0xE1, 0xFF, 0x01, 0x02, 0x03, 0x04},
expected: true,
},
{
name: "Invalid - too short",
ad: []byte{0x03, 0x16, 0xE1},
expected: false,
},
{
name: "Invalid - wrong type",
ad: []byte{0x08, 0x15, 0xE1, 0xFF, 0x01, 0x02, 0x03, 0x04},
expected: false,
},
{
name: "Invalid - wrong company ID",
ad: []byte{0x08, 0x16, 0xE1, 0xFE, 0x01, 0x02, 0x03, 0x04},
expected: false,
},
{
name: "Valid with minimum length",
ad: []byte{0x04, 0x16, 0xE1, 0xFF},
expected: true,
},
{
name: "Empty data",
ad: []byte{},
expected: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := checkMinewB7(tt.ad)
if result != tt.expected {
t.Errorf("checkMinewB7() = %v, expected %v", result, tt.expected)
}
})
}
}

func TestLoopADStructures(t *testing.T) {
tests := []struct {
name string
data []byte
ranges [][2]int
id string
expected model.BeaconEvent
}{
{
name: "Ingics beacon found",
data: []byte{0x08, 0xFF, 0x59, 0x00, 0x80, 0xBC, 0x34, 0x12, 0x05, 0x02, 0x01, 0x06},
ranges: [][2]int{{0, 8}, {8, 11}},
id: "test-beacon",
expected: model.BeaconEvent{
ID: "test-beacon",
Name: "test-beacon",
Battery: 0x1234,
Event: 0x05,
Type: "Ingics",
},
},
{
name: "Eddystone beacon found",
data: []byte{0x02, 0x01, 0x06, 0x12, 0x16, 0xAA, 0xFE, 0x20, 0x34, 0x12, 0x78, 0x56},
ranges: [][2]int{{0, 2}, {2, 14}},
id: "eddystone-test",
expected: model.BeaconEvent{
ID: "eddystone-test",
Name: "eddystone-test",
Battery: 0x1234,
Type: "Eddystone",
},
},
{
name: "Minew B7 beacon found",
data: []byte{0x08, 0x16, 0xE1, 0xFF, 0x01, 0x02, 0x03, 0x04, 0x02, 0x01, 0x06},
ranges: [][2]int{{0, 8}, {8, 11}},
id: "minew-test",
expected: model.BeaconEvent{
ID: "minew-test",
Name: "minew-test",
Type: "", // Minew B7 returns empty BeaconEvent
},
},
{
name: "No matching beacon type",
data: []byte{0x02, 0x01, 0x06, 0x03, 0x02, 0x01, 0x02},
ranges: [][2]int{{0, 2}, {2, 5}},
id: "unknown-test",
expected: model.BeaconEvent{},
},
{
name: "Invalid AD structure",
data: []byte{0x02, 0x01, 0x06, 0xFF, 0x01, 0x06},
ranges: [][2]int{{0, 2}, {2, 4}},
id: "invalid-test",
expected: model.BeaconEvent{},
},
{
name: "Empty data",
data: []byte{},
ranges: [][2]int{},
id: "empty-test",
expected: model.BeaconEvent{
ID: "empty-test",
Name: "empty-test",
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := LoopADStructures(tt.data, tt.ranges, tt.id)

if result.ID != tt.expected.ID {
t.Errorf("LoopADStructures() ID = %v, expected %v", result.ID, tt.expected.ID)
}
if result.Name != tt.expected.Name {
t.Errorf("LoopADStructures() Name = %v, expected %v", result.Name, tt.expected.Name)
}
if result.Type != tt.expected.Type {
t.Errorf("LoopADStructures() Type = %v, expected %v", result.Type, tt.expected.Type)
}
if result.Battery != tt.expected.Battery {
t.Errorf("LoopADStructures() Battery = %v, expected %v", result.Battery, tt.expected.Battery)
}
})
}
}

func TestLoopADStructuresPriority(t *testing.T) {
// Test that Ingics is checked first
data := []byte{0x08, 0xFF, 0x59, 0x00, 0x80, 0xBC, 0x34, 0x12, 0x05, 0x12, 0x16, 0xAA, 0xFE, 0x20, 0x78, 0x56}
ranges := [][2]int{{0, 8}, {8, 15}}

result := LoopADStructures(data, ranges, "priority-test")

// Should detect Ingics first, not Eddystone
if result.Type != "Ingics" {
t.Errorf("LoopADStructures() Type = %v, expected Ingics (priority test)", result.Type)
}
}

// Benchmark tests
func BenchmarkParseADFast(b *testing.B) {
data := []byte{0x02, 0x01, 0x06, 0x1A, 0xFF, 0x4C, 0x00, 0x02, 0x15, 0xE2, 0xC5, 0x6D, 0xB5, 0xDF, 0xFB, 0x48, 0xD2, 0xB0, 0x60, 0xD0, 0xF5, 0xA7, 0x10, 0x96, 0xE0, 0x00, 0x00, 0x00, 0x00, 0xC5}

for i := 0; i < b.N; i++ {
ParseADFast(data)
}
}

func BenchmarkRemoveFlagBytes(b *testing.B) {
data := []byte{0x02, 0x01, 0x06, 0x1A, 0xFF, 0x4C, 0x00, 0x02, 0x15, 0xE2, 0xC5}

for i := 0; i < b.N; i++ {
RemoveFlagBytes(data)
}
}

+ 0
- 294
test/distance_test.go Dosyayı Görüntüle

@@ -1,294 +0,0 @@
package test

import (
"testing"

"github.com/AFASystems/presence/internal/pkg/common/utils"
"github.com/AFASystems/presence/internal/pkg/model"
)

func TestCalculateDistance(t *testing.T) {
tests := []struct {
name string
adv model.BeaconAdvertisement
expected float64
}{
{
name: "Strong signal - close distance",
adv: model.BeaconAdvertisement{
RSSI: -30,
TXPower: "59", // 89 in decimal
},
expected: 0.89976, // Close to minimum
},
{
name: "Medium signal",
adv: model.BeaconAdvertisement{
RSSI: -65,
TXPower: "59",
},
expected: 1.5, // Medium distance
},
{
name: "Weak signal - far distance",
adv: model.BeaconAdvertisement{
RSSI: -95,
TXPower: "59",
},
expected: 8.0, // Far distance
},
{
name: "Equal RSSI and TX power",
adv: model.BeaconAdvertisement{
RSSI: -59,
TXPower: "59",
},
expected: 1.0, // Ratio = 1.0
},
{
name: "Very strong signal",
adv: model.BeaconAdvertisement{
RSSI: -10,
TXPower: "59",
},
expected: 0.89976, // Minimum distance
},
{
name: "Negative TX power (two's complement)",
adv: model.BeaconAdvertisement{
RSSI: -70,
TXPower: "C6", // -58 in decimal
},
expected: 1.2, // Medium distance
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := utils.CalculateDistance(tt.adv)
// Allow for small floating point differences
if result < tt.expected*0.9 || result > tt.expected*1.1 {
t.Errorf("CalculateDistance() = %v, expected around %v", result, tt.expected)
}
})
}
}

func TestCalculateDistanceEdgeCases(t *testing.T) {
tests := []struct {
name string
adv model.BeaconAdvertisement
expected float64
}{
{
name: "Zero RSSI",
adv: model.BeaconAdvertisement{
RSSI: 0,
TXPower: "59",
},
expected: 0.0,
},
{
name: "Invalid TX power",
adv: model.BeaconAdvertisement{
RSSI: -50,
TXPower: "XYZ",
},
expected: 0.0, // twosComp returns 0 for invalid input
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := utils.CalculateDistance(tt.adv)
if result != tt.expected {
t.Errorf("CalculateDistance() = %v, expected %v", result, tt.expected)
}
})
}
}

func TestValidateRSSI(t *testing.T) {
tests := []struct {
name string
rssi int64
expected bool
}{
{
name: "Valid RSSI - strong signal",
rssi: -30,
expected: true,
},
{
name: "Valid RSSI - weak signal",
rssi: -100,
expected: true,
},
{
name: "Valid RSSI - boundary low",
rssi: -120,
expected: true,
},
{
name: "Valid RSSI - boundary high",
rssi: 0,
expected: true,
},
{
name: "Invalid RSSI - too strong",
rssi: 10,
expected: false,
},
{
name: "Invalid RSSI - too weak",
rssi: -130,
expected: false,
},
{
name: "Invalid RSSI - just below boundary",
rssi: -121,
expected: false,
},
{
name: "Invalid RSSI - just above boundary",
rssi: 1,
expected: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := utils.ValidateRSSI(tt.rssi)
if result != tt.expected {
t.Errorf("ValidateRSSI() = %v, expected %v", result, tt.expected)
}
})
}
}

func TestValidateTXPower(t *testing.T) {
tests := []struct {
name string
txPower string
expected bool
}{
{
name: "Valid TX power - positive",
txPower: "59",
expected: true,
},
{
name: "Valid TX power - negative",
txPower: "C6",
expected: true,
},
{
name: "Valid TX power - zero",
txPower: "00",
expected: true,
},
{
name: "Valid TX power - max positive",
txPower: "7F",
expected: true,
},
{
name: "Valid TX power - max negative",
txPower: "80",
expected: true,
},
{
name: "Valid TX power - boundary negative",
txPower: "81", // -127
expected: true,
},
{
name: "Invalid TX power string",
txPower: "XYZ",
expected: true, // twosComp returns 0, which is valid
},
{
name: "Empty TX power",
txPower: "",
expected: true, // twosComp returns 0, which is valid
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := utils.ValidateTXPower(tt.txPower)
if result != tt.expected {
t.Errorf("ValidateTXPower() = %v, expected %v", result, tt.expected)
}
})
}
}

func TestCalculateDistanceConsistency(t *testing.T) {
// Test that the function is deterministic
adv := model.BeaconAdvertisement{
RSSI: -65,
TXPower: "59",
}

result1 := utils.CalculateDistance(adv)
result2 := utils.CalculateDistance(adv)

if result1 != result2 {
t.Errorf("CalculateDistance() is not deterministic: %v != %v", result1, result2)
}
}

func TestCalculateDistanceRealWorldScenarios(t *testing.T) {
scenarios := []struct {
name string
rssi int64
txPower string
expectedRange [2]float64 // min, max expected range
}{
{
name: "Beacon very close (1m)",
rssi: -45,
txPower: "59", // 89 decimal
expectedRange: [2]float64{0.5, 1.5},
},
{
name: "Beacon at medium distance (5m)",
rssi: -75,
txPower: "59",
expectedRange: [2]float64{3.0, 8.0},
},
{
name: "Beacon far away (15m)",
rssi: -95,
txPower: "59",
expectedRange: [2]float64{10.0, 25.0},
},
}

for _, scenario := range scenarios {
t.Run(scenario.name, func(t *testing.T) {
adv := model.BeaconAdvertisement{
RSSI: scenario.rssi,
TXPower: scenario.txPower,
}
result := utils.CalculateDistance(adv)

if result < scenario.expectedRange[0] || result > scenario.expectedRange[1] {
t.Errorf("CalculateDistance() = %v, expected range %v", result, scenario.expectedRange)
}
})
}
}

// Benchmark tests
func BenchmarkCalculateDistance(b *testing.B) {
adv := model.BeaconAdvertisement{
RSSI: -65,
TXPower: "59",
}

for i := 0; i < b.N; i++ {
utils.CalculateDistance(adv)
}
}

+ 0
- 568
test/mqtthandler_test.go Dosyayı Görüntüle

@@ -1,568 +0,0 @@
package mqtthandler

import (
"context"
"encoding/json"
"testing"

"github.com/AFASystems/presence/internal/pkg/model"
"github.com/segmentio/kafka-go"
)

// MockKafkaWriter implements a mock for kafka.Writer interface
type MockKafkaWriter struct {
Messages []kafka.Message
ShouldFail bool
WriteCount int
}

func (m *MockKafkaWriter) WriteMessages(ctx context.Context, msgs ...kafka.Message) error {
m.WriteCount++
if m.ShouldFail {
return &kafka.Error{
Err: &ErrMockWrite{},
Cause: nil,
Context: msgs[0],
}
}
m.Messages = append(m.Messages, msgs...)
return nil
}

// ErrMockWrite is a mock error for testing
type ErrMockWrite struct{}

func (e *ErrMockWrite) Error() string {
return "mock write error"
}

// Mock Kafka Close method (required for Writer interface)
func (m *MockKafkaWriter) Close() error {
return nil
}

func TestMqttHandlerJSONArrayInput(t *testing.T) {
tests := []struct {
name string
topicName []byte
message []byte
expectedMsgs int
shouldFail bool
}{
{
name: "Valid JSON array with multiple readings",
topicName: []byte("presence/gateway-001"),
message: []byte(`[{"timestamp":"2023-01-01T00:00:00Z","type":"Beacon","mac":"AA:BB:CC:DD:EE:FF","rssi":-65,"rawData":"0201060303E1FF1200001234"}]`),
expectedMsgs: 1,
shouldFail: false,
},
{
name: "JSON array with multiple beacons",
topicName: []byte("presence/gateway-002"),
message: []byte(`[{"timestamp":"2023-01-01T00:00:00Z","type":"Beacon","mac":"AA:BB:CC:DD:EE:FF","rssi":-65,"rawData":"0201060303E1FF1200001234"},{"timestamp":"2023-01-01T00:00:01Z","type":"Beacon","mac":"11:22:33:44:55:66","rssi":-75,"rawData":"0201060303E1FF1200005678"}]`),
expectedMsgs: 2,
shouldFail: false,
},
{
name: "JSON array with gateway reading (should be skipped)",
topicName: []byte("presence/gateway-003"),
message: []byte(`[{"timestamp":"2023-01-01T00:00:00Z","type":"Gateway","mac":"GG:AA:TT:EE:WA:Y","rssi":-20,"rawData":"gateway-data"},{"timestamp":"2023-01-01T00:00:01Z","type":"Beacon","mac":"AA:BB:CC:DD:EE:FF","rssi":-65,"rawData":"0201060303E1FF1200001234"}]`),
expectedMsgs: 1, // Only beacon should be processed
shouldFail: false,
},
{
name: "JSON array with only gateways (should be skipped)",
topicName: []byte("presence/gateway-004"),
message: []byte(`[{"timestamp":"2023-01-01T00:00:00Z","type":"Gateway","mac":"GG:AA:TT:EE:WA:Y","rssi":-20,"rawData":"gateway-data"}]`),
expectedMsgs: 0, // All gateways should be skipped
shouldFail: false,
},
{
name: "Invalid JSON array",
topicName: []byte("presence/gateway-005"),
message: []byte(`[{"timestamp":"2023-01-01T00:00:00Z","type":"Beacon","mac":"AA:BB:CC:DD:EE:FF","rssi":-65,"rawData":"0201060303E1FF1200001234"`),
expectedMsgs: 0,
shouldFail: false, // Should not panic, just log error
},
{
name: "Empty JSON array",
topicName: []byte("presence/gateway-006"),
message: []byte(`[]`),
expectedMsgs: 0,
shouldFail: false,
},
{
name: "JSON array with null readings",
topicName: []byte("presence/gateway-007"),
message: []byte(`[null]`),
expectedMsgs: 0,
shouldFail: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockWriter := &MockKafkaWriter{
Messages: make([]kafka.Message, 0),
ShouldFail: tt.shouldFail,
}

// Capture log output (you might want to use a test logger here)
MqttHandler(mockWriter, tt.topicName, tt.message)

if len(mockWriter.Messages) != tt.expectedMsgs {
t.Errorf("MqttHandler() wrote %d messages, expected %d", len(mockWriter.Messages), tt.expectedMsgs)
}

// Verify message content if we expected messages
if tt.expectedMsgs > 0 && len(mockWriter.Messages) > 0 {
for i, msg := range mockWriter.Messages {
var adv model.BeaconAdvertisement
err := json.Unmarshal(msg.Value, &adv)
if err != nil {
t.Errorf("MqttHandler() message %d is not valid BeaconAdvertisement JSON: %v", i, err)
}

// Verify hostname extraction
expectedHostname := "gateway-007" // Extracted from topicName
if adv.Hostname != expectedHostname {
t.Errorf("MqttHandler() hostname = %v, expected %v", adv.Hostname, expectedHostname)
}
}
}
})
}
}

func TestMqttHandlerCSVInput(t *testing.T) {
tests := []struct {
name string
topicName []byte
message []byte
shouldProcess bool
}{
{
name: "Valid CSV format",
topicName: []byte("presence/gateway-001"),
message: []byte("timestamp,AA:BB:CC:DD:EE:FF,-65,0201060303E1FF1200001234,1001,field6\n"),
shouldProcess: true,
},
{
name: "CSV with button data",
topicName: []byte("presence/gateway-002"),
message: []byte("timestamp,AA:BB:CC:DD:EE:FF,-65,02010612FF5901C0012345678,1234,field6\n"),
shouldProcess: true,
},
{
name: "CSV with insufficient fields",
topicName: []byte("presence/gateway-003"),
message: []byte("timestamp,AA:BB:CC:DD:EE:FF,-65,0201060303E1FF1200001234\n"),
shouldProcess: false, // Should log error and return early
},
{
name: "Empty CSV",
topicName: []byte("presence/gateway-004"),
message: []byte(""),
shouldProcess: false,
},
{
name: "CSV with wrong field count",
topicName: []byte("presence/gateway-005"),
message: []byte("field1,field2,field3\n"),
shouldProcess: false,
},
{
name: "CSV with non-numeric RSSI",
topicName: []byte("presence/gateway-006"),
message: []byte("timestamp,AA:BB:CC:DD:EE:FF,invalid,0201060303E1FF1200001234,1001,field6\n"),
shouldProcess: false, // Should fail on ParseInt
},
{
name: "CSV with non-numeric field6",
topicName: []byte("presence/gateway-007"),
message: []byte("timestamp,AA:BB:CC:DD:EE:FF,-65,0201060303E1FF1200001234,1001,invalid\n"),
shouldProcess: false, // Should fail on Atoi
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockWriter := &MockKafkaWriter{
Messages: make([]kafka.Message, 0),
ShouldFail: false,
}

// Note: The CSV handler in the original code has an os.Exit(2) which makes it untestable
// This test will fail due to os.Exit, but demonstrates the intended test structure
// In a real scenario, you'd want to refactor the code to avoid os.Exit
defer func() {
if r := recover(); r != nil {
// Expected due to os.Exit in original code
if tt.shouldProcess {
t.Errorf("MqttHandler() should not panic for valid CSV input: %v", r)
}
}
}()

// This will panic due to os.Exit(2) in the original code when field6 is invalid
// In a real refactor, you'd replace os.Exit with error return
if !tt.shouldProcess && string(tt.message) == "timestamp,AA:BB:CC:DD:EE:FF,-65,0201060303E1FF1200001234,1001,invalid\n" {
// Skip the case that will definitely panic
return
}

MqttHandler(mockWriter, tt.topicName, tt.message)

// CSV processing doesn't write to Kafka in the current implementation
if len(mockWriter.Messages) != 0 {
t.Errorf("MqttHandler() CSV processing should not write to Kafka, but wrote %d messages", len(mockWriter.Messages))
}
})
}
}

func TestParseButtonState(t *testing.T) {
tests := []struct {
name string
raw string
expected int64
}{
{
name: "Ingics button format - minimal length",
raw: "0201060303E1FF12",
expected: 0, // Too short for button field
},
{
name: "Ingics button format - exact length",
raw: "0201060303E1FF123456",
expected: 0x3456, // 13398 in decimal
},
{
name: "Ingics button format - longer",
raw: "0201060303E1FF12000012345678AB",
expected: 0x78AB, // 30891 in decimal
},
{
name: "Ingics button format - zero button",
raw: "0201060303E1FF1200000000",
expected: 0,
},
{
name: "Ingics button format - max button",
raw: "0201060303E1FF12FFFFFFFF",
expected: 0xFFFF, // 65535 in decimal
},
{
name: "Minew button format - minimal length",
raw: "02010612FF590",
expected: 0, // Too short for counter field
},
{
name: "Minew button format - exact length",
raw: "02010612FF590112",
expected: 0x12, // 18 in decimal
},
{
name: "Minew button format - longer",
raw: "02010612FF5901C0012345678",
expected: 0x78, // 120 in decimal
},
{
name: "Minew button format - zero counter",
raw: "02010612FF5901C000",
expected: 0,
},
{
name: "Minew button format - max counter",
raw: "02010612FF5901C0FF",
expected: 0xFF, // 255 in decimal
},
{
name: "Invalid prefix",
raw: "0201060303E1FE120000123456",
expected: 0,
},
{
name: "Invalid hex characters",
raw: "0201060303E1FF12ZZZZ",
expected: 0,
},
{
name: "Empty string",
raw: "",
expected: 0,
},
{
name: "Single character",
raw: "0",
expected: 0,
},
{
name: "Non-hex characters mixed",
raw: "0201060303E1FF12GHIJ",
expected: 0,
},
{
name: "Lowercase hex",
raw: "0201060303e1ff120000123456",
expected: 0, // Should be converted to uppercase
},
{
name: "Mixed case hex",
raw: "0201060303e1FF120000123456",
expected: 0x3456, // Should work after case conversion
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := parseButtonState(tt.raw)
if result != tt.expected {
t.Errorf("parseButtonState() = %v, expected %v", result, tt.expected)
}
})
}
}

func TestParseButtonStateEdgeCases(t *testing.T) {
// Test that Ingics format is checked before Minew format
ingicsRaw := "0201060303E1FF123456"
minewRaw := "02010612FF590112"

ingicsResult := parseButtonState(ingicsRaw)
minewResult := parseButtonState(minewRaw)

// Both should work, but Ingics should use bytes 34:38, Minew should use bytes 22:24
if ingicsResult != 0x3456 {
t.Errorf("parseButtonState() Ingics format failed: got %v, want %v", ingicsResult, 0x3456)
}

if minewResult != 0x12 {
t.Errorf("parseButtonState() Minew format failed: got %v, want %v", minewResult, 0x12)
}

// Test with overlapping patterns (unlikely but good to test)
overlapRaw := "0201060303E1FF122FF590112"
overlapResult := parseButtonState(overlapRaw)
// Should match Ingics pattern and use bytes 34:38
expectedOverlap := int64(0) // There are no bytes 34:38 in this string
if overlapResult != expectedOverlap {
t.Errorf("parseButtonState() overlap case: got %v, want %v", overlapResult, expectedOverlap)
}
}

func TestHostnameExtraction(t *testing.T) {
tests := []struct {
name string
topicName []byte
expectedHost string
}{
{
name: "Simple topic",
topicName: []byte("presence/gateway-001"),
expectedHost: "gateway-001",
},
{
name: "Topic with multiple segments",
topicName: []byte("home/office/floor3/gateway-A123"),
expectedHost: "home",
},
{
name: "Topic with numbers only",
topicName: []byte("12345"),
expectedHost: "12345",
},
{
name: "Single segment topic",
topicName: []byte("singlegateway"),
expectedHost: "singlegateway",
},
{
name: "Topic with empty segments",
topicName: []byte("//gateway//001//"),
expectedHost: "", // First non-empty segment after split
},
{
name: "Empty topic",
topicName: []byte(""),
expectedHost: "",
},
{
name: "Topic with special characters",
topicName: []byte("presence/gateway-with-dashes_and_underscores"),
expectedHost: "presence",
},
{
name: "Topic starting with slash",
topicName: []byte("/presence/gateway-001"),
expectedHost: "", // First segment is empty
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockWriter := &MockKafkaWriter{
Messages: make([]kafka.Message, 0),
}

// Create a simple JSON message that will be processed
message := []byte(`[{"timestamp":"2023-01-01T00:00:00Z","type":"Beacon","mac":"AA:BB:CC:DD:EE:FF","rssi":-65,"rawData":"0201060303E1FF1200001234"}]`)

MqttHandler(mockWriter, tt.topicName, message)

if len(mockWriter.Messages) > 0 {
var adv model.BeaconAdvertisement
err := json.Unmarshal(mockWriter.Messages[0].Value, &adv)
if err != nil {
t.Errorf("Failed to unmarshal Kafka message: %v", err)
return
}

if adv.Hostname != tt.expectedHost {
t.Errorf("Hostname extraction = %v, expected %v", adv.Hostname, tt.expectedHost)
}
}
})
}
}

func TestKafkaWriteFailure(t *testing.T) {
mockWriter := &MockKafkaWriter{
Messages: make([]kafka.Message, 0),
ShouldFail: true,
}

topicName := []byte("presence/test-gateway")
message := []byte(`[{"timestamp":"2023-01-01T00:00:00Z","type":"Beacon","mac":"AA:BB:CC:DD:EE:FF","rssi":-65,"rawData":"0201060303E1FF1200001234"}]`)

// This should handle the write error gracefully (it sleeps for 1 second)
MqttHandler(mockWriter, topicName, message)

// No messages should have been written successfully
if len(mockWriter.Messages) != 0 {
t.Errorf("Expected 0 messages on write failure, got %d", len(mockWriter.Messages))
}

// Should have attempted to write
if mockWriter.WriteCount != 1 {
t.Errorf("Expected 1 write attempt, got %d", mockWriter.WriteCount)
}
}

func TestMessageMarshaling(t *testing.T) {
tests := []struct {
name string
reading model.RawReading
}{
{
name: "Standard beacon reading",
reading: model.RawReading{
Timestamp: "2023-01-01T00:00:00Z",
Type: "Beacon",
MAC: "AA:BB:CC:DD:EE:FF",
RSSI: -65,
RawData: "0201060303E1FF1200001234",
},
},
{
name: "Beacon with special characters in MAC",
reading: model.RawReading{
Timestamp: "2023-01-01T00:00:00Z",
Type: "Beacon",
MAC: "AA:BB:CC:DD:EE:FF",
RSSI: -75,
RawData: "02010612FF5901C0012345678",
},
},
{
name: "Beacon with extreme RSSI values",
reading: model.RawReading{
Timestamp: "2023-01-01T00:00:00Z",
Type: "Beacon",
MAC: "11:22:33:44:55:66",
RSSI: -120,
RawData: "0201060303E1FF120000ABCD",
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockWriter := &MockKafkaWriter{
Messages: make([]kafka.Message, 0),
}

// Create JSON array with our test reading
readings := []model.RawReading{tt.reading}
message, err := json.Marshal(readings)
if err != nil {
t.Fatalf("Failed to marshal test reading: %v", err)
}

topicName := []byte("presence/test-gateway")
MqttHandler(mockWriter, topicName, message)

if len(mockWriter.Messages) != 1 {
t.Errorf("Expected 1 message, got %d", len(mockWriter.Messages))
return
}

// Verify the message can be unmarshaled back to BeaconAdvertisement
var adv model.BeaconAdvertisement
err = json.Unmarshal(mockWriter.Messages[0].Value, &adv)
if err != nil {
t.Errorf("Failed to unmarshal Kafka message: %v", err)
return
}

// Verify fields match the original reading
if adv.MAC != tt.reading.MAC {
t.Errorf("MAC mismatch: got %v, want %v", adv.MAC, tt.reading.MAC)
}
if adv.RSSI != int64(tt.reading.RSSI) {
t.Errorf("RSSI mismatch: got %v, want %v", adv.RSSI, tt.reading.RSSI)
}
if adv.Data != tt.reading.RawData {
t.Errorf("Data mismatch: got %v, want %v", adv.Data, tt.reading.RawData)
}
})
}
}

// Benchmark tests
func BenchmarkParseButtonState(b *testing.B) {
raw := "0201060303E1FF12000012345678AB"
for i := 0; i < b.N; i++ {
parseButtonState(raw)
}
}

func BenchmarkMqttHandlerJSON(b *testing.B) {
mockWriter := &MockKafkaWriter{
Messages: make([]kafka.Message, 0),
}

topicName := []byte("presence/benchmark-gateway")
message := []byte(`[{"timestamp":"2023-01-01T00:00:00Z","type":"Beacon","mac":"AA:BB:CC:DD:EE:FF","rssi":-65,"rawData":"0201060303E1FF1200001234"}]`)

b.ResetTimer()
for i := 0; i < b.N; i++ {
MqttHandler(mockWriter, topicName, message)
mockWriter.Messages = mockWriter.Messages[:0] // Reset messages
}
}

func BenchmarkMqttHandlerMultipleBeacons(b *testing.B) {
mockWriter := &MockKafkaWriter{
Messages: make([]kafka.Message, 0),
}

topicName := []byte("presence/benchmark-gateway")
message := []byte(`[{"timestamp":"2023-01-01T00:00:00Z","type":"Beacon","mac":"AA:BB:CC:DD:EE:FF","rssi":-65,"rawData":"0201060303E1FF1200001234"},{"timestamp":"2023-01-01T00:00:01Z","type":"Beacon","mac":"11:22:33:44:55:66","rssi":-75,"rawData":"02010612FF5901C0012345678"}]`)

b.ResetTimer()
for i := 0; i < b.N; i++ {
MqttHandler(mockWriter, topicName, message)
mockWriter.Messages = mockWriter.Messages[:0] // Reset messages
}
}

+ 0
- 644
test/typeMethods_test.go Dosyayı Görüntüle

@@ -1,644 +0,0 @@
package model

import (
"testing"
)

func TestBeaconEventHash(t *testing.T) {
tests := []struct {
name string
be BeaconEvent
expected []byte
}{
{
name: "Basic beacon event",
be: BeaconEvent{
ID: "beacon-1",
Name: "Test Beacon",
Type: "Ingics",
Battery: 1000,
Event: 1,
},
expected: nil, // We'll test that it produces a consistent hash
},
{
name: "Same beacon with different battery should produce same hash",
be: BeaconEvent{
ID: "beacon-1",
Name: "Test Beacon",
Type: "Ingics",
Battery: 1009, // 1000 + 9, should round to 1000
Event: 1,
},
expected: nil,
},
{
name: "Different ID should produce different hash",
be: BeaconEvent{
ID: "beacon-2",
Name: "Test Beacon",
Type: "Ingics",
Battery: 1000,
Event: 1,
},
expected: nil,
},
{
name: "Different event should produce different hash",
be: BeaconEvent{
ID: "beacon-1",
Name: "Test Beacon",
Type: "Ingics",
Battery: 1000,
Event: 2,
},
expected: nil,
},
{
name: "Zero values",
be: BeaconEvent{
ID: "",
Name: "",
Type: "",
Battery: 0,
Event: 0,
},
expected: nil,
},
{
name: "Special characters",
be: BeaconEvent{
ID: "beacon!@#$%^&*()",
Name: "Test\nBeacon\tWith\tTabs",
Type: "Special-Type_123",
Battery: 1000,
Event: 1,
},
expected: nil,
},
}

// Test that Hash produces consistent results
hashes := make([][]byte, len(tests))

for i, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
hashes[i] = tt.be.Hash()

// Hash should always be 32 bytes for SHA256
if len(hashes[i]) != 32 {
t.Errorf("Hash() length = %v, expected 32", len(hashes[i]))
}

// Hash should not be empty unless all fields are empty
if len(hashes[i]) == 0 && (tt.be.ID != "" || tt.be.Name != "" || tt.be.Type != "") {
t.Errorf("Hash() should not be empty for non-empty beacon event")
}
})
}

// Test that same input produces same hash
be1 := BeaconEvent{
ID: "test-beacon",
Name: "Test",
Type: "Ingics",
Battery: 1000,
Event: 1,
}

hash1 := be1.Hash()
hash2 := be1.Hash()

if string(hash1) != string(hash2) {
t.Errorf("Hash() should be deterministic: %v != %v", hash1, hash2)
}

// Test battery rounding
beBattery1 := BeaconEvent{
ID: "test-beacon",
Name: "Test",
Type: "Ingics",
Battery: 1005, // Should round to 1000
Event: 1,
}

beBattery2 := BeaconEvent{
ID: "test-beacon",
Name: "Test",
Type: "Ingics",
Battery: 1000,
Event: 1,
}

hashBattery1 := beBattery1.Hash()
hashBattery2 := beBattery2.Hash()

if string(hashBattery1) != string(hashBattery2) {
t.Errorf("Hash() with battery rounding should be same: %v != %v", hashBattery1, hashBattery2)
}
}

func TestBeaconEventToJSON(t *testing.T) {
tests := []struct {
name string
be BeaconEvent
expectedError bool
}{
{
name: "Valid beacon event",
be: BeaconEvent{
ID: "beacon-1",
Name: "Test Beacon",
Type: "Ingics",
Battery: 1000,
Event: 1,
},
expectedError: false,
},
{
name: "Empty beacon event",
be: BeaconEvent{},
expectedError: false,
},
{
name: "Beacon with special characters",
be: BeaconEvent{
ID: "beacon-with-special-chars!@#$%",
Name: "Name with unicode: 测试",
Type: "Type-With-Dashes_and_underscores",
Battery: 12345,
Event: 255,
},
expectedError: false,
},
{
name: "Beacon with maximum values",
be: BeaconEvent{
ID: "max-beacon",
Name: "Maximum Values Test",
Type: "MaxType",
Battery: 0xFFFFFFFF, // Max uint32
Event: 2147483647, // Max int32
},
expectedError: false,
},
{
name: "Zero values",
be: BeaconEvent{
ID: "",
Name: "",
Type: "",
Battery: 0,
Event: 0,
},
expectedError: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := tt.be.ToJSON()

if (err != nil) != tt.expectedError {
t.Errorf("ToJSON() error = %v, expectedError %v", err, tt.expectedError)
return
}

if !tt.expectedError {
// Result should not be nil
if result == nil {
t.Error("ToJSON() result should not be nil")
return
}

// Result should not be empty JSON
if string(result) == "null" {
t.Error("ToJSON() result should not be 'null'")
}

// Basic JSON validation - should start and end with braces for object
if len(result) > 0 && result[0] != '{' && result[0] != '[' {
t.Errorf("ToJSON() result should be valid JSON, got: %s", string(result))
}
}
})
}
}

func TestConvertStructToMap(t *testing.T) {
tests := []struct {
name string
input any
expectedError bool
}{
{
name: "Valid BeaconEvent",
input: BeaconEvent{ID: "test", Type: "Ingics"},
expectedError: false,
},
{
name: "Valid HTTPLocation",
input: HTTPLocation{Method: "POST", ID: "test"},
expectedError: false,
},
{
name: "Valid struct",
input: struct{ Name string }{Name: "test"},
expectedError: false,
},
{
name: "Nil input",
input: nil,
expectedError: false,
},
{
name: "String input",
input: "test string",
expectedError: false,
},
{
name: "Map input",
input: map[string]any{"test": "value"},
expectedError: false,
},
{
name: "Slice input",
input: []string{"test1", "test2"},
expectedError: false,
},
{
name: "Complex struct with nested structures",
input: struct {
SimpleField string
Nested struct {
InnerField int
}
SliceField []string
MapField map[string]any
}{
SimpleField: "test",
Nested: struct{ InnerField int }{InnerField: 123},
SliceField: []string{"a", "b", "c"},
MapField: map[string]any{"key": "value"},
},
expectedError: false,
},
{
name: "Struct with channel field",
input: struct{ Ch chan int }{Ch: make(chan int)},
expectedError: true, // Channels cannot be marshaled to JSON
},
{
name: "Struct with function field",
input: struct{ Func func() }{Func: func() {}},
expectedError: true, // Functions cannot be marshaled to JSON
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := convertStructToMap(tt.input)

if (err != nil) != tt.expectedError {
t.Errorf("convertStructToMap() error = %v, expectedError %v", err, tt.expectedError)
return
}

if !tt.expectedError {
// Result should be a map
if result == nil && tt.input != nil {
t.Error("convertStructToMap() result should not be nil for non-nil input")
}

// For valid inputs, result should be a map
if tt.input != nil {
if _, ok := result.(map[string]any); !ok && result != nil {
t.Errorf("convertStructToMap() result should be a map[string]any, got %T", result)
}
}
}
})
}
}

func TestHTTPLocationRedisHashable(t *testing.T) {
tests := []struct {
name string
location HTTPLocation
expectedError bool
}{
{
name: "Valid location",
location: HTTPLocation{
Method: "POST",
PreviousConfidentLocation: "room1",
Distance: 5.5,
ID: "beacon-123",
Location: "room2",
LastSeen: 1634567890,
},
expectedError: false,
},
{
name: "Minimal location",
location: HTTPLocation{
Method: "GET",
ID: "beacon-1",
},
expectedError: false,
},
{
name: "Zero values",
location: HTTPLocation{},
expectedError: false,
},
{
name: "Location with special characters",
location: HTTPLocation{
Method: "CUSTOM",
ID: "beacon-with-special-chars!@#$%",
Location: "Room-with-unicode: 测试",
Distance: -123.456, // Negative distance
},
expectedError: false,
},
{
name: "Maximum values",
location: HTTPLocation{
Method: "MAX",
PreviousConfidentLocation: "max-room",
Distance: 9223372036854775807, // Max int64 as float64
ID: "max-beacon-id-12345678901234567890",
Location: "max-location-name",
LastSeen: 9223372036854775807, // Max int64
},
expectedError: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := tt.location.RedisHashable()

if (err != nil) != tt.expectedError {
t.Errorf("HTTPLocation.RedisHashable() error = %v, expectedError %v", err, tt.expectedError)
return
}

if !tt.expectedError {
// Result should be a map
if result == nil {
t.Error("HTTPLocation.RedisHashable() result should not be nil")
return
}

resultMap, ok := result.(map[string]any)
if !ok {
t.Errorf("HTTPLocation.RedisHashable() result should be a map[string]any, got %T", result)
return
}

// Check that expected fields are present
expectedFields := []string{"method", "previous_confident_location", "distance", "id", "location", "last_seen"}
for _, field := range expectedFields {
if _, exists := resultMap[field]; !exists {
t.Errorf("HTTPLocation.RedisHashable() missing expected field: %s", field)
}
}

// Check JSON tags are respected
if _, exists := resultMap["Method"]; exists {
t.Error("HTTPLocation.RedisHashable() should use JSON field names, not struct field names")
}

if _, exists := resultMap["method"]; !exists {
t.Error("HTTPLocation.RedisHashable() should contain 'method' field (JSON tag)")
}
}
})
}
}

func TestBeaconEventRedisHashable(t *testing.T) {
tests := []struct {
name string
be BeaconEvent
expectedError bool
}{
{
name: "Valid beacon event",
be: BeaconEvent{
ID: "beacon-123",
Name: "Test Beacon",
Type: "Ingics",
Battery: 1000,
Event: 1,
},
expectedError: false,
},
{
name: "Minimal beacon event",
be: BeaconEvent{
ID: "test",
},
expectedError: false,
},
{
name: "Zero values",
be: BeaconEvent{},
expectedError: false,
},
{
name: "Beacon event with special characters",
be: BeaconEvent{
ID: "beacon-!@#$%^&*()",
Name: "Name with unicode: 测试",
Type: "Special-Type_123",
Battery: 12345,
Event: 255,
},
expectedError: false,
},
{
name: "Maximum values",
be: BeaconEvent{
ID: "max-beacon-id",
Name: "Maximum Values Test",
Type: "MaxType",
Battery: 0xFFFFFFFF, // Max uint32
Event: 2147483647, // Max int32
},
expectedError: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := tt.be.RedisHashable()

if (err != nil) != tt.expectedError {
t.Errorf("BeaconEvent.RedisHashable() error = %v, expectedError %v", err, tt.expectedError)
return
}

if !tt.expectedError {
// Result should be a map
if result == nil {
t.Error("BeaconEvent.RedisHashable() result should not be nil")
return
}

resultMap, ok := result.(map[string]any)
if !ok {
t.Errorf("BeaconEvent.RedisHashable() result should be a map[string]any, got %T", result)
return
}

// Check that expected fields are present
expectedFields := []string{"name", "id", "type", "battery", "event"}
for _, field := range expectedFields {
if _, exists := resultMap[field]; !exists {
t.Errorf("BeaconEvent.RedisHashable() missing expected field: %s", field)
}
}

// Check JSON tags are respected (BeaconEvent fields are not tagged with JSON, so field names should be lowercase)
if _, exists := resultMap["Name"]; exists {
t.Error("BeaconEvent.RedisHashable() should use lowercase field names")
}

if _, exists := resultMap["name"]; !exists {
t.Error("BeaconEvent.RedisHashable() should contain 'name' field")
}
}
})
}
}

func TestHashConsistencyWithBatteryRounding(t *testing.T) {
// Test that Hash() is consistent with battery rounding
testCases := []struct {
battery1 uint32
battery2 uint32
shouldMatch bool
}{
{1000, 1009, true}, // Same rounding range
{1000, 1010, false}, // Different rounding range
{0, 9, true}, // Zero range
{100, 104, true}, // Same range (100-109 rounds to 100)
{100, 110, false}, // Different ranges
{4294967295, 4294967289, true}, // Max value range
}

for i, tc := range testCases {
t.Run(fmt.Sprintf("BatteryRoundCase_%d", i), func(t *testing.T) {
be1 := BeaconEvent{
ID: "test-beacon",
Name: "Test",
Type: "Ingics",
Battery: tc.battery1,
Event: 1,
}

be2 := BeaconEvent{
ID: "test-beacon",
Name: "Test",
Type: "Ingics",
Battery: tc.battery2,
Event: 1,
}

hash1 := be1.Hash()
hash2 := be2.Hash()

hashesMatch := string(hash1) == string(hash2)

if hashesMatch != tc.shouldMatch {
t.Errorf("Hash consistency mismatch: battery1=%d, battery2=%d, hashesMatch=%v, shouldMatch=%v",
tc.battery1, tc.battery2, hashesMatch, tc.shouldMatch)
}
})
}
}

func TestJSONMarshalUnmarshalRoundtrip(t *testing.T) {
original := BeaconEvent{
ID: "roundtrip-test",
Name: "Roundtrip Test",
Type: "TestType",
Battery: 12345,
Event: 42,
}

// Test that ToJSON produces valid JSON that can be unmarshaled back
jsonData, err := original.ToJSON()
if err != nil {
t.Fatalf("ToJSON() error: %v", err)
}

var unmarshaled BeaconEvent
err = json.Unmarshal(jsonData, &unmarshaled)
if err != nil {
t.Fatalf("json.Unmarshal() error: %v", err)
}

// Verify roundtrip integrity
if unmarshaled.ID != original.ID {
t.Errorf("Roundtrip ID mismatch: got %v, want %v", unmarshaled.ID, original.ID)
}
if unmarshaled.Name != original.Name {
t.Errorf("Roundtrip Name mismatch: got %v, want %v", unmarshaled.Name, original.Name)
}
if unmarshaled.Type != original.Type {
t.Errorf("Roundtrip Type mismatch: got %v, want %v", unmarshaled.Type, original.Type)
}
if unmarshaled.Battery != original.Battery {
t.Errorf("Roundtrip Battery mismatch: got %v, want %v", unmarshaled.Battery, original.Battery)
}
if unmarshaled.Event != original.Event {
t.Errorf("Roundtrip Event mismatch: got %v, want %v", unmarshaled.Event, original.Event)
}
}

// Benchmark tests
func BenchmarkHash(b *testing.B) {
be := BeaconEvent{
ID: "benchmark-beacon",
Name: "Benchmark Test",
Type: "Ingics",
Battery: 1000,
Event: 1,
}

for i := 0; i < b.N; i++ {
be.Hash()
}
}

func BenchmarkToJSON(b *testing.B) {
be := BeaconEvent{
ID: "benchmark-beacon",
Name: "Benchmark Test",
Type: "Ingics",
Battery: 1000,
Event: 1,
}

for i := 0; i < b.N; i++ {
be.ToJSON()
}
}

func BenchmarkConvertStructToMap(b *testing.B) {
be := BeaconEvent{
ID: "benchmark-beacon",
Name: "Benchmark Test",
Type: "Ingics",
Battery: 1000,
Event: 1,
}

for i := 0; i < b.N; i++ {
convertStructToMap(be)
}
}

Yükleniyor…
İptal
Kaydet