Skip to content

Commit 8ac4ad8

Browse files
Merge pull request #2 from MerzSebastian/feature/analogNode
Feature/analog node
2 parents db6f0a5 + dc3b500 commit 8ac4ad8

File tree

4 files changed

+130
-18
lines changed

4 files changed

+130
-18
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ A visual programming interface for Arduino controllers built with React and Reac
1212

1313
Weekend project with occasional updates. This is my first React project, so best practices may not always be followed.
1414

15+
things to keep in mind:
16+
- only connect 1 line to a input of a block. if you want to connect more use a or element beforehand. I dont got any automation in there to do that automatically. will come
17+
1518
## Features
1619

1720
- Drag-and-drop block interface for Arduino programming

arduino/arduino.ino

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#define OP_LATCH 16
1717
#define OP_PULSE 17
1818
#define OP_TOGGLE 18
19+
#define OP_ANALOG_RANGE 19
1920

2021
const int MAX_INSTRUCTIONS = 300;
2122
const int MAX_VARIABLES = 60;
@@ -49,36 +50,24 @@ int lastButtonState[MAX_VARIABLES] = {LOW}; // Store the last stable button sta
4950
// WebSerial message structure
5051
#define START_BYTE 0x7E
5152

52-
// #define WOKWI // uncomment when using wokwi
53+
// For Wokwi simulation, do not change the next line, it will get replaced with regex
54+
// #define WOKWI
5355

5456
void setup() {
5557
Serial.begin(9600);
5658

5759
// Check if we should load from WebSerial or use fixed bytecode
5860
#ifdef WOKWI
59-
// For Wokwi simulation, use fixed bytecode
61+
// For Wokwi simulation, do not change the next line, it will get replaced with regex
6062
byte myBytecode[] = {1, 3, 1, 4, 1, 2, 2, 5, 1, 6, 3, 3, 0, 3, 4, 1, 3, 2, 2, 3, 6, 3, 17, 4, 100, 0, 232, 3, 18, 2, 5, 0, 16, 1, 3, 6, 0, 11, 4, 0, 6, 4, 5, 7, 4, 5, 7};
6163

6264
instructionLength = sizeof(myBytecode) / sizeof(myBytecode[0]);
6365
for (int i = 0; i < instructionLength; i++) {
6466
instructions[i] = myBytecode[i];
6567
}
66-
67-
Serial.print("Loaded ");
68-
Serial.print(instructionLength);
69-
Serial.println(" instructions for Wokwi simulation");
7068
#else
7169
// For real Arduino, try to load from EEPROM first
7270
loadFromEEPROM();
73-
74-
// If no valid program in EEPROM, wait for WebSerial transmission
75-
if (instructionLength == 0) {
76-
Serial.println("READY");
77-
} else {
78-
Serial.print("Loaded ");
79-
Serial.print(instructionLength);
80-
Serial.println(" instructions from EEPROM");
81-
}
8271
#endif
8372
}
8473

@@ -398,6 +387,19 @@ void executeInstructions() {
398387
variables[outputVar] = toggleState[outputVar];
399388
break;
400389
}
390+
case OP_ANALOG_RANGE: {
391+
byte inputVar = instructions[pc++];
392+
unsigned int min = instructions[pc++];
393+
min |= (instructions[pc++] << 8);
394+
unsigned int max = instructions[pc++];
395+
max |= (instructions[pc++] << 8);
396+
byte outputVar = instructions[pc++];
397+
398+
int value = variables[inputVar];
399+
variables[outputVar] = (value >= min && value <= max) ? 1 : 0;
400+
break;
401+
}
402+
401403
}
402404
}
403405
}

logic-editor/src/App.tsx

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ const blockTypes = [
5050
{ type: 'latchNode', label: 'LATCH', color: 'bg-orange-500' },
5151
{ type: 'pulseNode', label: 'PULSE (beta)', color: 'bg-cyan-500' },
5252
{ type: 'toggleNode', label: 'TOGGLE', color: 'bg-pink-500' },
53+
{ type: 'analogRangeNode', label: 'ANALOG RANGE', color: 'bg-teal-500' },
5354
];
5455

5556
// === Node Definitions ===
@@ -77,6 +78,36 @@ function LatchNode({ data, id }: any) {
7778
);
7879
}
7980

81+
function AnalogRangeNode({ data, id }: any) {
82+
return (
83+
<div className={`${nodeBaseClasses} w-40 bg-teal-500 border-2 border-teal-700`}>
84+
<div className={titleClasses}>ANALOG RANGE</div>
85+
<Handle type="target" position={Position.Left} id="in" className={handleClasses} />
86+
<div className="flex space-x-1 mt-1">
87+
<input
88+
type="number"
89+
min="0"
90+
max="1023"
91+
value={data.min || 0}
92+
onChange={(e) => data.onChangeMin(id, parseInt(e.target.value))}
93+
className={`${inputClasses} w-16`}
94+
placeholder="Min"
95+
/>
96+
<input
97+
type="number"
98+
min="0"
99+
max="1023"
100+
value={data.max || 1023}
101+
onChange={(e) => data.onChangeMax(id, parseInt(e.target.value))}
102+
className={`${inputClasses} w-16`}
103+
placeholder="Max"
104+
/>
105+
</div>
106+
<Handle type="source" position={Position.Right} id="out" className={handleClasses} />
107+
</div>
108+
);
109+
}
110+
80111
function ToggleNode({ data, id }: any) {
81112
return (
82113
<div className={`${nodeBaseClasses} w-30 h-15 bg-pink-500 border-2 border-pink-800`}>
@@ -315,8 +346,25 @@ export default function App() {
315346
latchNode: LatchNode,
316347
pulseNode: PulseNode,
317348
toggleNode: ToggleNode,
349+
analogRangeNode: AnalogRangeNode,
318350
}), []);
319351

352+
const handleMinChange = useCallback((nodeId: string, min: number) => {
353+
setNodes((nds) =>
354+
nds.map((n) =>
355+
n.id === nodeId ? { ...n, data: { ...n.data, min } } : n
356+
)
357+
);
358+
}, [setNodes]);
359+
360+
const handleMaxChange = useCallback((nodeId: string, max: number) => {
361+
setNodes((nds) =>
362+
nds.map((n) =>
363+
n.id === nodeId ? { ...n, data: { ...n.data, max } } : n
364+
)
365+
);
366+
}, [setNodes]);
367+
320368
const handleInitialStateChange = useCallback((nodeId: string, initialState: number) => {
321369
setNodes((nds) =>
322370
nds.map((n) =>
@@ -687,6 +735,8 @@ export default function App() {
687735
onChangeInitialState: n.type === 'latchNode' || n.type === 'toggleNode' ? handleInitialStateChange : undefined,
688736
onChangePulseLength: n.type === 'pulseNode' ? handlePulseLengthChange : undefined,
689737
onChangeInterval: n.type === 'pulseNode' ? handleIntervalChange : undefined,
738+
onChangeMin: n.type === 'analogRangeNode' ? handleMinChange : undefined,
739+
onChangeMax: n.type === 'analogRangeNode' ? handleMaxChange : undefined,
690740
}
691741
}))}
692742
edges={edges}

logic-editor/src/bytecode-gen.ts

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Enhanced bytecode-gen.ts with Latch and Timer support
1+
// Enhanced bytecode-gen.ts with Analog Range support
22
export const OP_SET_PIN_MODE_INPUT = 1;
33
export const OP_SET_PIN_MODE_OUTPUT = 2;
44
export const OP_READ_PIN = 3;
@@ -14,6 +14,7 @@ export const OP_XOR = 15;
1414
export const OP_LATCH = 16;
1515
export const OP_PULSE = 17;
1616
export const OP_TOGGLE = 18;
17+
export const OP_ANALOG_RANGE = 19;
1718
export const OP_DELAY = 30;
1819

1920
// Types for the logic configuration
@@ -26,6 +27,8 @@ interface NodeData {
2627
label: string;
2728
inputs?: number;
2829
pin?: string;
30+
min?: number;
31+
max?: number;
2932
initialState?: number;
3033
pulseLength?: number;
3134
interval?: number;
@@ -62,7 +65,7 @@ export function generateBytecode(config: LogicConfig): number[] {
6265
const { nodes, edges } = config;
6366
const instructions: number[] = [];
6467

65-
// Build graph and in-degree map
68+
// Build graph
6669
const graph: Record<string, string[]> = {};
6770
const inDegree: Record<string, number> = {};
6871
const nodeDict: Record<string, Node> = {};
@@ -81,6 +84,30 @@ export function generateBytecode(config: LogicConfig): number[] {
8184
inDegree[target] += 1;
8285
}
8386

87+
// Simple check: if an input node is connected to an analog node
88+
const inputUsesAnalog: Record<string, boolean> = {};
89+
90+
for (const node of nodes) {
91+
if (node.type === 'inputNode') {
92+
// Check if this input is connected to any analog node
93+
const checkConnectedToAnalog = (nodeId: string, visited: Set<string> = new Set()): boolean => {
94+
if (visited.has(nodeId)) return false;
95+
visited.add(nodeId);
96+
97+
const currentNode = nodeDict[nodeId];
98+
if (currentNode.type === 'analogRangeNode') return true;
99+
100+
for (const neighbor of graph[nodeId]) {
101+
if (checkConnectedToAnalog(neighbor, visited)) return true;
102+
}
103+
104+
return false;
105+
};
106+
107+
inputUsesAnalog[node.id] = checkConnectedToAnalog(node.id);
108+
}
109+
}
110+
84111
// Topological sort
85112
const queue: string[] = [];
86113
for (const [nodeId, deg] of Object.entries(inDegree)) {
@@ -132,7 +159,13 @@ export function generateBytecode(config: LogicConfig): number[] {
132159
if (node.type === 'inputNode' && node.data.pin) {
133160
const pin = parseInt(node.data.pin, 10);
134161
const varIndex = varIndexMap[nodeId];
135-
instructions.push(OP_READ_PIN);
162+
163+
// Use analog read if connected to an analog node
164+
if (inputUsesAnalog[nodeId]) {
165+
instructions.push(OP_READ_ANALOG_PIN);
166+
} else {
167+
instructions.push(OP_READ_PIN);
168+
}
136169
instructions.push(pin);
137170
instructions.push(varIndex);
138171
} else if (node.type === 'outputNode' && node.data.pin) {
@@ -154,6 +187,30 @@ export function generateBytecode(config: LogicConfig): number[] {
154187
instructions.push(OP_WRITE_PIN);
155188
instructions.push(pin);
156189
instructions.push(sourceVar);
190+
} else if (node.type === 'analogRangeNode') {
191+
// Handle analog range node
192+
const inputVars: number[] = [];
193+
for (const edge of edges) {
194+
if (edge.target === nodeId) {
195+
const sourceNodeId = edge.source;
196+
if (varIndexMap[sourceNodeId] !== undefined) {
197+
inputVars.push(varIndexMap[sourceNodeId]);
198+
}
199+
}
200+
}
201+
202+
if (inputVars.length >= 1 && varIndexMap[nodeId] !== undefined) {
203+
const min = node.data.min || 0;
204+
const max = node.data.max || 1023;
205+
206+
instructions.push(OP_ANALOG_RANGE);
207+
instructions.push(inputVars[0]); // Input variable
208+
instructions.push(min & 0xFF); // Min value low byte
209+
instructions.push((min >> 8) & 0xFF); // Min value high byte
210+
instructions.push(max & 0xFF); // Max value low byte
211+
instructions.push((max >> 8) & 0xFF); // Max value high byte
212+
instructions.push(varIndexMap[nodeId]); // Output variable
213+
}
157214
} else {
158215
// Logic gate node
159216
const gateType = node.type;

0 commit comments

Comments
 (0)