Skip to content

Commit 09aaff2

Browse files
Merge pull request #5 from MerzSebastian/feature/add-analog-comparer
Feature/add analog comparer
2 parents b983765 + b7968e9 commit 09aaff2

File tree

4 files changed

+248
-3
lines changed

4 files changed

+248
-3
lines changed

arduino/arduino.ino

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@
1717
#define OP_PULSE 17
1818
#define OP_TOGGLE 18
1919
#define OP_ANALOG_RANGE 19
20+
#define OP_ANALOG_COMPARE_GT 20
21+
#define OP_ANALOG_COMPARE_GE 21
22+
#define OP_ANALOG_COMPARE_LT 22
23+
#define OP_ANALOG_COMPARE_LE 23
24+
#define OP_ANALOG_COMPARE_EQ 24
25+
#define OP_ANALOG_COMPARE_NE 25
2026

2127
const int MAX_INSTRUCTIONS = 300;
2228
const int MAX_VARIABLES = 60;
@@ -399,7 +405,51 @@ void executeInstructions() {
399405
variables[outputVar] = (value >= min && value <= max) ? 1 : 0;
400406
break;
401407
}
402-
408+
case OP_ANALOG_COMPARE_GT: {
409+
byte aVar = instructions[pc++];
410+
byte bVar = instructions[pc++];
411+
byte outputVar = instructions[pc++];
412+
variables[outputVar] = (variables[aVar] > variables[bVar]) ? 1 : 0;
413+
break;
414+
}
415+
case OP_ANALOG_COMPARE_GE: {
416+
byte aVar = instructions[pc++];
417+
byte bVar = instructions[pc++];
418+
byte outputVar = instructions[pc++];
419+
variables[outputVar] = (variables[aVar] >= variables[bVar]) ? 1 : 0;
420+
break;
421+
}
422+
case OP_ANALOG_COMPARE_LT: {
423+
byte aVar = instructions[pc++];
424+
byte bVar = instructions[pc++];
425+
byte outputVar = instructions[pc++];
426+
variables[outputVar] = (variables[aVar] < variables[bVar]) ? 1 : 0;
427+
break;
428+
}
429+
case OP_ANALOG_COMPARE_LE: {
430+
byte aVar = instructions[pc++];
431+
byte bVar = instructions[pc++];
432+
byte outputVar = instructions[pc++];
433+
variables[outputVar] = (variables[aVar] <= variables[bVar]) ? 1 : 0;
434+
break;
435+
}
436+
case OP_ANALOG_COMPARE_EQ: {
437+
byte aVar = instructions[pc++];
438+
byte bVar = instructions[pc++];
439+
byte outputVar = instructions[pc++];
440+
// Add a small tolerance for analog value comparisons
441+
variables[outputVar] = (abs(variables[aVar] - variables[bVar]) < 5) ? 1 : 0;
442+
break;
443+
}
444+
case OP_ANALOG_COMPARE_NE: {
445+
byte aVar = instructions[pc++];
446+
byte bVar = instructions[pc++];
447+
byte outputVar = instructions[pc++];
448+
// Add a small tolerance for analog value comparisons
449+
variables[outputVar] = (abs(variables[aVar] - variables[bVar]) >= 5) ? 1 : 0;
450+
break;
451+
}
452+
403453
}
404454
}
405455
}

logic-editor/src/App.test.tsx

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -727,3 +727,114 @@ test('larger project without OR. connecting two buttons to same input', () => {
727727

728728
expect(bytecodeToString(generateBytecode(test as any))).toEqual(expectedBytecode);
729729
});
730+
731+
test('analogComparerNode test', () => {
732+
const test = {
733+
"nodes": [
734+
{
735+
"id": "dndnode_0",
736+
"type": "analogComparerNode",
737+
"position": {
738+
"x": 355.08583015996766,
739+
"y": 267.65848888782415
740+
},
741+
"data": {
742+
"label": "analogComparerNode",
743+
"inputs": 2,
744+
"selectedBoard": "arduino_nano"
745+
},
746+
"width": 160,
747+
"height": 103
748+
},
749+
{
750+
"id": "dndnode_1",
751+
"type": "inputNode",
752+
"position": {
753+
"x": 177.975220378801,
754+
"y": 253.12988417921287
755+
},
756+
"data": {
757+
"label": "inputNode",
758+
"inputs": 2,
759+
"selectedBoard": "arduino_nano",
760+
"pin": "14"
761+
},
762+
"width": 97,
763+
"height": 59,
764+
"selected": false,
765+
"dragging": false,
766+
"positionAbsolute": {
767+
"x": 177.975220378801,
768+
"y": 253.12988417921287
769+
}
770+
},
771+
{
772+
"id": "dndnode_2",
773+
"type": "inputNode",
774+
"position": {
775+
"x": 173.82419046205496,
776+
"y": 319.5463628471503
777+
},
778+
"data": {
779+
"label": "inputNode",
780+
"inputs": 2,
781+
"selectedBoard": "arduino_nano",
782+
"pin": "15"
783+
},
784+
"width": 97,
785+
"height": 59,
786+
"selected": false,
787+
"dragging": false,
788+
"positionAbsolute": {
789+
"x": 173.82419046205496,
790+
"y": 319.5463628471503
791+
}
792+
},
793+
{
794+
"id": "dndnode_3",
795+
"type": "outputNode",
796+
"position": {
797+
"x": 569.5557091918491,
798+
"y": 304.3259198190813
799+
},
800+
"data": {
801+
"label": "outputNode",
802+
"inputs": 2,
803+
"selectedBoard": "arduino_nano",
804+
"pin": "13"
805+
},
806+
"width": 97,
807+
"height": 59,
808+
"selected": false,
809+
"dragging": false
810+
}
811+
],
812+
"edges": [
813+
{
814+
"source": "dndnode_1",
815+
"sourceHandle": "out",
816+
"target": "dndnode_0",
817+
"targetHandle": "a",
818+
"id": "reactflow__edge-dndnode_1out-dndnode_0a"
819+
},
820+
{
821+
"source": "dndnode_2",
822+
"sourceHandle": "out",
823+
"target": "dndnode_0",
824+
"targetHandle": "b",
825+
"id": "reactflow__edge-dndnode_2out-dndnode_0b"
826+
},
827+
{
828+
"source": "dndnode_0",
829+
"sourceHandle": "out",
830+
"target": "dndnode_3",
831+
"targetHandle": "in",
832+
"id": "reactflow__edge-dndnode_0out-dndnode_3in"
833+
}
834+
],
835+
"board": "arduino_nano"
836+
};
837+
const expectedBytecode = "1,14,1,15,2,13,5,14,0,5,15,1,20,0,1,2,4,13,2";
838+
839+
expect(bytecodeToString(generateBytecode(test as any))).toEqual(expectedBytecode);
840+
});

logic-editor/src/App.tsx

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ const blockTypes = [
5151
{ type: 'pulseNode', label: 'PULSE (beta)', color: 'bg-cyan-500' },
5252
{ type: 'toggleNode', label: 'TOGGLE', color: 'bg-pink-500' },
5353
{ type: 'analogRangeNode', label: 'ANALOG RANGE', color: 'bg-teal-500' },
54+
{ type: 'analogComparerNode', label: 'ANALOG COMPARER', color: 'bg-indigo-500' },
5455
];
5556

5657
// === Node Definitions ===
@@ -108,6 +109,33 @@ function AnalogRangeNode({ data, id }: any) {
108109
);
109110
}
110111

112+
function AnalogComparerNode({ data, id }: any) {
113+
return (
114+
<div className={`${nodeBaseClasses} w-40 bg-indigo-500 border-2 border-indigo-700`}>
115+
<div className={titleClasses}>ANALOG COMPARER</div>
116+
<Handle type="target" position={Position.Left} id="a" className={handleClasses} style={{ top: '35%' }} />
117+
<Handle type="target" position={Position.Left} id="b" className={handleClasses} style={{ top: '55%' }} />
118+
<div className='absolute left-1 top-[35%] -mt-3 text-sm'>A</div>
119+
<div className='absolute left-1 top-[55%] -mt-3 text-sm'>B</div>
120+
121+
<select
122+
value={data.comparisonType || '>'}
123+
onChange={(e) => data.onChangeComparisonType(id, e.target.value)}
124+
className={`${inputClasses} mt-6 w-full`}
125+
>
126+
<option value=">">A &gt; B</option>
127+
<option value=">=">A &gt;= B</option>
128+
<option value="<">A &lt; B</option>
129+
<option value="<=">A &lt;= B</option>
130+
<option value="==">A == B</option>
131+
<option value="!=">A != B</option>
132+
</select>
133+
134+
<Handle type="source" position={Position.Right} id="out" className={handleClasses} />
135+
</div>
136+
);
137+
}
138+
111139
function ToggleNode({ data, id }: any) {
112140
return (
113141
<div className={`${nodeBaseClasses} w-30 h-15 bg-pink-500 border-2 border-pink-800`}>
@@ -347,8 +375,17 @@ export default function App() {
347375
pulseNode: PulseNode,
348376
toggleNode: ToggleNode,
349377
analogRangeNode: AnalogRangeNode,
378+
analogComparerNode: AnalogComparerNode,
350379
}), []);
351380

381+
const handleComparisonTypeChange = useCallback((nodeId: string, comparisonType: string) => {
382+
setNodes((nds) =>
383+
nds.map((n) =>
384+
n.id === nodeId ? { ...n, data: { ...n.data, comparisonType } } : n
385+
)
386+
);
387+
}, [setNodes]);
388+
352389
const handleMinChange = useCallback((nodeId: string, min: number) => {
353390
setNodes((nds) =>
354391
nds.map((n) =>
@@ -736,7 +773,8 @@ export default function App() {
736773
onChangePulseLength: n.type === 'pulseNode' ? handlePulseLengthChange : undefined,
737774
onChangeInterval: n.type === 'pulseNode' ? handleIntervalChange : undefined,
738775
onChangeMin: n.type === 'analogRangeNode' ? handleMinChange : undefined,
739-
onChangeMax: n.type === 'analogRangeNode' ? handleMaxChange : undefined,
776+
onChangeMax: n.type === 'analogRangeNode' ? handleMaxChange : undefined,
777+
onChangeComparisonType: n.type === 'analogComparerNode' ? handleComparisonTypeChange : undefined,
740778
}
741779
}))}
742780
edges={edges}

logic-editor/src/bytecode-gen.ts

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ export const OP_LATCH = 16;
1515
export const OP_PULSE = 17;
1616
export const OP_TOGGLE = 18;
1717
export const OP_ANALOG_RANGE = 19;
18+
export const OP_ANALOG_COMPARE_GT = 20;
19+
export const OP_ANALOG_COMPARE_GE = 21;
20+
export const OP_ANALOG_COMPARE_LT = 22;
21+
export const OP_ANALOG_COMPARE_LE = 23;
22+
export const OP_ANALOG_COMPARE_EQ = 24;
23+
export const OP_ANALOG_COMPARE_NE = 25;
1824
export const OP_DELAY = 30;
1925

2026
// Types for the logic configuration
@@ -32,6 +38,7 @@ interface NodeData {
3238
initialState?: number;
3339
pulseLength?: number;
3440
interval?: number;
41+
comparisonType?: string;
3542
}
3643

3744
interface Node {
@@ -188,7 +195,7 @@ export function generateBytecode(config: LogicConfig): number[] {
188195
visited.add(nodeId);
189196

190197
const currentNode = nodeDict[nodeId];
191-
if (currentNode.type === 'analogRangeNode') return true;
198+
if (currentNode.type === 'analogRangeNode' || currentNode.type === 'analogComparerNode') return true;
192199

193200
for (const neighbor of graph[nodeId]) {
194201
if (checkConnectedToAnalog(neighbor, visited)) return true;
@@ -310,6 +317,45 @@ export function generateBytecode(config: LogicConfig): number[] {
310317

311318
// Handle different node types
312319
switch (gateType) {
320+
case 'analogComparerNode': {
321+
// Find inputs A and B
322+
let aVar = -1;
323+
let bVar = -1;
324+
325+
for (const edge of edges) {
326+
if (edge.target === nodeId) {
327+
const sourceNodeId = edge.source;
328+
if (varIndexMap[sourceNodeId] !== undefined) {
329+
if (edge.targetHandle === 'a') {
330+
aVar = varIndexMap[sourceNodeId];
331+
} else if (edge.targetHandle === 'b') {
332+
bVar = varIndexMap[sourceNodeId];
333+
}
334+
}
335+
}
336+
}
337+
338+
if (aVar >= 0 && bVar >= 0 && varIndexMap[nodeId] !== undefined) {
339+
const comparisonType = node.data.comparisonType || '>';
340+
let opcode;
341+
342+
switch (comparisonType) {
343+
case '>': opcode = OP_ANALOG_COMPARE_GT; break;
344+
case '>=': opcode = OP_ANALOG_COMPARE_GE; break;
345+
case '<': opcode = OP_ANALOG_COMPARE_LT; break;
346+
case '<=': opcode = OP_ANALOG_COMPARE_LE; break;
347+
case '==': opcode = OP_ANALOG_COMPARE_EQ; break;
348+
case '!=': opcode = OP_ANALOG_COMPARE_NE; break;
349+
default: opcode = OP_ANALOG_COMPARE_GT;
350+
}
351+
352+
instructions.push(opcode);
353+
instructions.push(aVar);
354+
instructions.push(bVar);
355+
instructions.push(varIndexMap[nodeId]);
356+
}
357+
break;
358+
}
313359
case 'notNode': {
314360
const inputVars: number[] = [];
315361
for (const edge of edges) {

0 commit comments

Comments
 (0)