@@ -4,18 +4,16 @@ import { LaserMetrics, RoomV2Metrics } from '@luna/contexts/api/model/types';
44import { Breakpoint , useBreakpoint } from '@luna/hooks/useBreakpoint' ;
55import { useEventListener } from '@luna/hooks/useEventListener' ;
66import { flattenRoomV2Metrics } from '@luna/screens/home/admin/helpers/FlatRoomV2Metrics' ;
7+ import { MonitorCriterion } from '@luna/screens/home/admin/helpers/MonitorCriterion' ;
78import { MonitorInspector } from '@luna/screens/home/admin/MonitorInspector' ;
89import { HomeContent } from '@luna/screens/home/HomeContent' ;
10+ import * as rgb from '@luna/utils/rgb' ;
911import { throttle } from '@luna/utils/schedule' ;
1012import { Vec2 } from '@luna/utils/vec2' ;
1113import { Button } from '@heroui/react' ;
1214import { IconRefresh } from '@tabler/icons-react' ;
1315import { Set } from 'immutable' ;
14- import {
15- LIGHTHOUSE_COLOR_CHANNELS ,
16- LIGHTHOUSE_COLS ,
17- LIGHTHOUSE_FRAME_BYTES ,
18- } from 'nighthouse/browser' ;
16+ import { LIGHTHOUSE_COLS , LIGHTHOUSE_FRAME_BYTES } from 'nighthouse/browser' ;
1917import {
2018 useCallback ,
2119 useContext ,
@@ -24,6 +22,7 @@ import {
2422 useRef ,
2523 useState ,
2624} from 'react' ;
25+ import { Bounded , isBounded } from '@luna/utils/bounded' ;
2726
2827export function MonitorView ( ) {
2928 const [ maxSize , setMaxSize ] = useState ( { width : 0 , height : 0 } ) ;
@@ -58,6 +57,7 @@ export function MonitorView() {
5857
5958 const [ focusedRoom , setSelectedRoom ] = useState < number > ( ) ;
6059 const [ hoveredRoom , setHoveredRoom ] = useState < number > ( ) ;
60+ const [ criterion , setCriterion ] = useState < MonitorCriterion > ( ) ;
6161
6262 const getLatestMetrics = useCallback ( async ( ) => {
6363 // setMetrics(testMetrics); // TODO: change back from test data to fetched data
@@ -75,7 +75,10 @@ export function MonitorView() {
7575 } , [ getLatestMetrics ] ) ;
7676
7777 const roomMetrics = useMemo (
78- ( ) => ( metrics ?. rooms ?? [ ] ) as RoomV2Metrics [ ] ,
78+ ( ) =>
79+ ( metrics ?. rooms ?? [ ] ) . filter (
80+ room => room . api_version === 2
81+ ) as RoomV2Metrics [ ] ,
7982 [ metrics ?. rooms ]
8083 ) ;
8184
@@ -84,40 +87,108 @@ export function MonitorView() {
8487 [ roomMetrics ]
8588 ) ;
8689
90+ const valueToNumberOrNull = useCallback (
91+ ( value : number | string | boolean | Bounded < number > | null | undefined ) => {
92+ if ( value === null || value === undefined ) {
93+ return null ;
94+ }
95+ if ( typeof value === 'object' && isBounded ( value ) ) {
96+ return value . value ;
97+ }
98+ return + value ;
99+ } ,
100+ [ ]
101+ ) ;
102+
103+ const criterionValues = useMemo ( ( ) => {
104+ if ( criterion === undefined ) return undefined ;
105+ switch ( criterion . type ) {
106+ case 'room' :
107+ return flatRoomMetrics . flatMap ( room =>
108+ [ ...Array ( room . responsive_lamps . total ) ] . map ( ( ) =>
109+ valueToNumberOrNull ( room [ criterion . key ] )
110+ )
111+ ) ;
112+ case 'lamp' :
113+ return roomMetrics . flatMap ( room =>
114+ room . lamp_metrics . map ( lamp =>
115+ valueToNumberOrNull ( lamp [ criterion . key ] )
116+ )
117+ ) ;
118+ }
119+ } , [ criterion , flatRoomMetrics , roomMetrics , valueToNumberOrNull ] ) ;
120+
121+ const normalizedCriterionValues = useMemo ( ( ) => {
122+ if ( criterionValues === undefined ) return undefined ;
123+ if ( criterionValues . length === 0 ) return [ ] ;
124+ const nonNulls = criterionValues . filter ( v => v !== null ) as number [ ] ;
125+ const min = nonNulls . reduce ( ( x , y ) => Math . min ( x , y ) ) ;
126+ const max = nonNulls . reduce ( ( x , y ) => Math . max ( x , y ) ) ;
127+ return criterionValues . map ( x => ( x === null ? 0 : ( x - min ) / ( max - min ) ) ) ;
128+ } , [ criterionValues ] ) ;
129+
130+ const criterionColormap = useMemo ( ( ) => {
131+ if ( criterion !== undefined ) {
132+ switch ( criterion . type ) {
133+ case 'room' :
134+ switch ( criterion . key ) {
135+ case 'board_temperature' :
136+ case 'core_temperature' :
137+ return [ rgb . BLUE , rgb . RED ] ;
138+ case 'responding' :
139+ return [ rgb . GREEN , rgb . RED ] ;
140+ }
141+ break ;
142+ case 'lamp' :
143+ switch ( criterion . key ) {
144+ case 'responding' :
145+ case 'fuse_tripped' :
146+ return [ rgb . GREEN , rgb . RED ] ;
147+ }
148+ break ;
149+ }
150+ }
151+ return [ rgb . BLACK , rgb . RED , rgb . YELLOW , rgb . WHITE ] ;
152+ } , [ criterion ] ) ;
153+
87154 // fill the frame with colors according to the metrics data
88155 const frame = useMemo ( ( ) => {
89156 const frame = new Uint8Array ( LIGHTHOUSE_FRAME_BYTES ) ;
90- // alternate between light and dark color to visualize room borders
91- let parity = false ;
92- let i = 0 ;
93- for ( const room of roomMetrics ) {
94- if ( room . api_version !== 2 ) continue ;
95- const endIdx = i + LIGHTHOUSE_COLOR_CHANNELS * room . lamp_metrics . length ;
96- // controller works?
97- if ( room . controller_metrics . responding ) {
98- let lampIdx = 0 ;
99- for ( ; i < endIdx ; i += LIGHTHOUSE_COLOR_CHANNELS ) {
100- // lamp works?
101- if ( room . lamp_metrics [ lampIdx ] . responding ) {
102- frame [ i + 1 ] = parity ? 255 : 128 ; // green
103- } else {
104- // lamp down -> magenta
105- frame [ i ] = parity ? 255 : 128 ;
106- frame [ i + 2 ] = parity ? 255 : 128 ;
157+ let windowIdx = 0 ;
158+
159+ const hasActiveCriterion = normalizedCriterionValues !== undefined ;
160+ if ( hasActiveCriterion ) {
161+ for ( const value of normalizedCriterionValues as number [ ] ) {
162+ const color = rgb . lerpMultiple ( criterionColormap , value ) ;
163+ rgb . setAt ( windowIdx , color , frame ) ;
164+ windowIdx ++ ;
165+ }
166+ } else {
167+ // alternate between light and dark color to visualize room borders
168+ let parity = false ;
169+ const parityDim = ( c : rgb . Color ) => rgb . scale ( c , parity ? 1 : 0.6 ) ;
170+ for ( const room of roomMetrics ) {
171+ const lampCount = room . lamp_metrics . length ;
172+ // controller works?
173+ if ( room . controller_metrics . responding ) {
174+ for ( let lampIdx = 0 ; lampIdx < lampCount ; lampIdx ++ ) {
175+ // lamp works?
176+ const color = parityDim (
177+ room . lamp_metrics [ lampIdx ] . responding ? rgb . GREEN : rgb . MAGENTA
178+ ) ;
179+ rgb . setAt ( windowIdx + lampIdx , color , frame ) ;
107180 }
108- lampIdx ++ ;
109- }
110- } else {
111- // controller down
112- for ( ; i < endIdx ; i += 3 ) {
113- frame [ i ] = parity ? 255 : 128 ; // red
181+ } else {
182+ // controller down
183+ rgb . fillAt ( windowIdx , lampCount , parityDim ( rgb . RED ) , frame ) ;
114184 }
185+ windowIdx += lampCount ;
186+ parity = ! parity ;
115187 }
116- parity = ! parity ;
117188 }
118189
119190 return frame ;
120- } , [ roomMetrics ] ) ;
191+ } , [ criterionColormap , normalizedCriterionValues , roomMetrics ] ) ;
121192
122193 const [ roomsByWindow , windowsByRoom ] = useMemo < [ number [ ] , number [ ] [ ] ] > ( ( ) => {
123194 const roomsByWindow : number [ ] = [ ] ;
@@ -207,6 +278,8 @@ export function MonitorView() {
207278 className = { isCompact ? '' : 'flex flex-row justify-end grow-0 w-1/3' }
208279 >
209280 < MonitorInspector
281+ criterion = { criterion }
282+ setCriterion = { setCriterion }
210283 flatRoomMetrics = { focusedFlatRoomMetrics }
211284 lampMetrics = { focusedLampMetrics }
212285 />
0 commit comments