Restructure project with OpenSCAD redesign and v1 legacy code

- Move legacy FreeCAD files to v1/
- Add OpenSCAD programmatic CAD designs
- Add README and AGENTS.md documentation
- Add .gitignore
- Update firmware to v2 architecture
This commit is contained in:
Michael Winter 2026-03-29 14:24:42 +02:00
parent 468cac4dff
commit 0fbf33b756
164 changed files with 4009 additions and 9 deletions

28
.gitignore vendored Normal file
View file

@ -0,0 +1,28 @@
# Arduino / ESP32
*.ino.cpp
*.lst
*.elf
*.eep
*.hex
# Build directories
build/
# PlatformIO
.pio/
.vscode/.pf-*
# VS Code
.vscode/
.idea/
# macOS
.DS_Store
# Logs
*.log
# Backup files
*~
*.bak
*.swp

213
AGENTS.md Normal file
View file

@ -0,0 +1,213 @@
# AGENTS.md - Sirens Project
## Project Overview
This is an Arduino/ESP32 embedded systems project for controlling sirens. The codebase consists of multiple Arduino sketches (.ino files) for different hardware components.
## Directory Structure
```
sirens/
├── src/
│ ├── siren_remote_control_esp32_v2/ # Remote control unit
│ │ └── siren_remote_control_esp32_v2.ino
│ ├── siren_controller_esp32_simple_station_v2_multi/ # Station controller
│ │ └── siren_controller_esp32_simple_station_v2_multi.ino
│ └── B_G431B_ESC1_motor_controller/ # SimpleFOC motor controller
│ └── B_G431B_ESC1_motor_controller.ino
├── v1/ # Version 1 designs
├── designs/ # CAD files (OpenSCAD, STL)
└── AGENTS.md # This file
```
## Build Commands
### Current State
- **No command-line build system configured** - Uses Arduino IDE
- Upload via Arduino IDE or PlatformIO (recommended for future)
### Future: PlatformIO Migration (TODO)
```bash
# Once PlatformIO is configured:
pio run # Build all projects
pio run -e <environment> # Build specific environment
pio run -t upload # Upload to device
pio device monitor # Monitor serial output
```
### Running a Single Test
- **No unit tests exist** - Embedded projects typically test via hardware
- For now, manual testing on physical hardware is required
## Libraries
The project uses the following Arduino/ESP32 libraries:
| Library | Purpose |
|---------|---------|
| WiFi | ESP32 WiFi connectivity |
| Wire | I2C communication |
| SSD1306Ascii / SSD1306AsciiWire | OLED display (128x64) |
| ArduinoOSCWiFi | OSC protocol over WiFi |
| AiEsp32RotaryEncoder | Rotary encoder input |
| arduino-timer | Timer/async operations |
| Preferences | ESP32 NVS storage |
| SimpleFOC | Motor control (B-G431B-ESC1) |
| SimpleFOCDrivers | SimpleFOC driver utilities |
## Code Style Guidelines
### General Conventions
- **Language**: C++ (Arduino framework)
- **File extension**: `.ino` (Arduino sketch)
- **Naming**: camelCase for variables and functions
- **Constants**: UPPER_SNAKE_CASE with `const` or `#define`
### Arduino Sketch Structure
```cpp
// 1. Includes
#include "WiFi.h"
#include <Wire.h>
#include <SSD1306Ascii.h>
// 2. Global constants
const int sirenCount = 4;
const char* ssid = "sirening";
// 3. Global variables
int menuSelect = 0;
// 4. Function prototypes (if needed)
void setupEncoderButton(AiEsp32RotaryEncoder& eb, char* val);
// 5. setup() - runs once at boot
void setup() {
Serial.begin(115200);
// initialization code...
}
// 6. loop() - runs continuously
void loop() {
// main logic...
}
```
### Functions
- Keep functions focused and reasonably sized (<100 lines)
- Use descriptive names: `updateFreq()`, `killAll()`, `setupOSC()`
- Group related functions together
- Use helper functions to reduce duplication in loop()
### Types
- Use explicit types: `int`, `float`, `bool`, `unsigned long`
- Prefer `const` for values that won't change
- Use `float` for floating-point values (no `double` on ESP32)
### Error Handling
- Return early on errors:
```cpp
if(!currentSense.init()){
Serial.println("Current sense init failed.");
return;
}
```
- Use Serial for debugging output with descriptive messages
- Validate input ranges before use:
```cpp
if (input < 500.0 && input >= 0.0) {
updateFreq(input);
}
```
### Interrupt Service Routines (ISR)
- Mark ISRs with `IRAM_ATTR` on ESP32:
```cpp
void IRAM_ATTR readFreqEncoderISR() {
encoderButton1.readEncoder_ISR();
}
```
- Keep ISRs minimal - only do essential work
### Display/UI Code
- Use helper functions for display updates:
```cpp
void updateDisplayValue(String string) {
oled.set2X();
oled.setCursor(12, 5);
oled.print(string);
oled.clearToEOL();
}
```
### OSC Communication
- Use lambda callbacks for OSC subscriptions:
```cpp
OscWiFi.subscribe(settings_port, "/freq", [](const OscMessage& m) {
float input = m.arg<float>(0);
// handle message...
});
```
### Preferences (NVS)
- Open with explicit mode:
```cpp
prefs.begin("prefs", RO_MODE); // false = read/write, true = read only
prefs.putUInt("ports", ports);
prefs.getUInt("ports");
prefs.end();
```
### Serial Communication
- Use `Serial.begin(115200)` for debug output
- Use `HardwareSerial` for additional UARTs:
```cpp
HardwareSerial freqSerial(1);
freqSerial.begin(115200, SERIAL_8N1, 18, 19);
```
### Comments
- Use TODO comments for future work:
```cpp
//TODO: These should be bound to a publication of the frequency directly from the siren
```
- Comment out disabled code with explanation
- Keep comments brief and purposeful
### Formatting
- Use 2-space indentation (Arduino default)
- Opening brace on same line
- Space after keywords: `if (` not `if(`
## Future Improvements
1. **Add PlatformIO**: Create `platformio.ini` for command-line builds
2. **Add unit tests**: Use PlatformIO unit testing for non-hardware logic
3. **Consolidate common code**: Extract shared utilities into a common library
4. **Add CI/CD**: GitHub Actions for build verification
5. **Document hardware**: Add wiring diagrams and pinouts
## Hardware Targets
- **ESP32** (WiFi + BLE)
- **B-G431B-ESC1** motor controller (SimpleFOC compatible)
- **OLED Display**: SSD1306 128x64 I2C
- **Rotary Encoder**: AiEsp32RotaryEncoder
## Development Notes
- Serial monitor uses 115200 baud
- WiFi AP: `sirening` / `alarm_11735`
- OSC ports: 54000 (settings), 54001 (state)
- Motor control uses FOC (Field-Oriented Control) via SimpleFOC

163
README.md
View file

@ -0,0 +1,163 @@
# Tunable Sirens
A programmable, tunable siren system using BLDC motors with FOC (Field-Oriented Control).
## Overview
This project implements a tunable siren where the frequency is controlled remotely via OSC (Open Sound Control) messages over WiFi. The system uses a DJI 2212 920KV BLDC motor driven by a B-G431B-ESC1 controller running SimpleFOC. Multiple stations can be controlled independently or simultaneously.
## Hardware
| Component | Part |
|-----------|------|
| Motor | DJI 2212 920KV BLDC |
| Motor Controller | STMicroelectronics B-G431B-ESC1 |
| Processors | ESP32 WROOM (one per station, one for remote) |
| Display | SSD1306 128x64 OLED (on both remote and station) |
| Input | AiEsp32 Rotary Encoder (on both remote and station) |
## Architecture
```
┌──────────────────────┐
│ Remote Control │
│ (ESP32 + OLED) │
┌─────────────────┐ OSC (WiFi) │ 192.168.4.200 │
│ External OSC │ ───────────────┤ Port: 54001 │
│ (Any client) │ │ - Acts as WiFi AP │
└─────────────────┘ │ - Routes to stations│
└──────────┬───────────┘
│ OSC forward
│ (optional path)
┌────────────────┐ ┌────────────────┐
│ Station 1 │ │ Station N │
│ 192.168.4.201 │ ... │ 192.168.4.20X │
│ Port: 54000 │ │ Port: 54000 │
└───────┬────────┘ └────────┬────────┘
│ │
│ ┌─────────────────┴─────────┐
└────────►│ B-G431B-ESC1 │
│ SimpleFOC │
│ DJI 2212 920KV BLDC │
└────────────────────────────┘
```
## Communication Modes
### 1. Via Remote Control (Router Mode)
The remote control acts as a WiFi access point and forwards OSC messages to stations. This allows controlling multiple sirens from a single entry point.
- **Remote IP**: `192.168.4.200`
- **Port**: `54001` (receives commands)
### 2. Direct to Station
Send OSC messages directly to each station's IP address:
- **Station 1**: `192.168.4.201:54000`
- **Station 2**: `192.168.4.202:54000`
- And so on...
### WiFi Setup
- **SSID**: `sirening`
- **Password**: `alarm_11735`
- **IP Range**: `192.168.4.200` (remote) + `201-208` (stations)
## OSC Protocol
### Messages
| Address | Arguments | Description |
|---------|-----------|-------------|
| `/freq` | `motorIndex, frequency` | Set frequency in Hz (0-500) |
| `/kill` | — | Stop siren (set frequency to 0) |
| `/state` | `motorIndex, frequency` | Station reports current state |
### Example (Python)
```python
from pythonosc import osc_message_builder
from pythonosc.udp_client import SimpleUDPClient
client = SimpleUDPClient("192.168.4.201", 54000)
# Set frequency to 100Hz for motor 1
msg = osc_message_builder.OscMessageBuilder(address="/freq")
msg.add_arg(1) # motorIndex (1-indexed)
msg.add_arg(100.0) # frequency in Hz
client.send(msg.build())
# Kill/stop the siren
msg = osc_message_builder.OscMessageBuilder(address="/kill")
client.send(msg.build())
```
### Station Response
The station reports its state back on port `54001`:
```python
# Incoming message: /state motorIndex frequency
# Example: /state 1 100.0
```
## Local Controls
Both the remote control and station controllers have a local interface:
### Rotary Encoder
| Action | Function |
|--------|----------|
| Rotate | Change frequency (or navigate menus) |
| Press | Select siren / confirm selection |
| Double-press (remote) | Kill all sirens |
### OLED Display
- Shows current frequency
- Shows selected siren number
- Shows connection status (WiFi)
## Directory Structure
```
sirens/
├── README.md # This file
├── AGENTS.md # Developer notes for AI agents
├── src/ # Current firmware (v2)
│ ├── siren_remote_control_esp32_v2/ # Remote control unit
│ ├── siren_controller_esp32_*/ # Station controller
│ └── B_G431B_ESC1_motor_controller/ # Motor controller (SimpleFOC)
├── v1/ # Legacy hardware and firmware
│ ├── src/ # Old versions of controller code
│ └── designs/ # Legacy CAD files (FreeCAD)
├── designs/ # Current CAD files
│ ├── openscad/ # OpenSCAD source files
│ │ ├── siren_redesign.scad # Main siren design
│ │ └── box_snap.scad # Controller box
│ ├── stl/ # 3D print files
│ └── 3mf/ # 3MF print files
```
## CAD Files
- **Main siren**: `designs/openscad/siren_redesign.scad`
- **Controller box**: `designs/openscad/box_snap.scad`
- **STL files**: `designs/stl/`
## Building & Flashing
Uses Arduino framework. For development, [PlatformIO](https://platformio.org/) is recommended for command-line builds.
See `AGENTS.md` for details on libraries and coding conventions.
## Version History
| Version | Description |
|---------|-------------|
| v1 | Legacy system (FreeCAD) |
| v2 (current) | Programmatic CAD design (OpenSCAD), improved code structure |

BIN
designs/3mf/base.3mf Normal file

Binary file not shown.

BIN
designs/3mf/hub.3mf Normal file

Binary file not shown.

Binary file not shown.

BIN
designs/3mf/leg.3mf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
designs/3mf/motor_frame.3mf Normal file

Binary file not shown.

BIN
designs/3mf/rotor.3mf Normal file

Binary file not shown.

Binary file not shown.

BIN
designs/3mf/stator.3mf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
designs/3mf/stator_top.3mf Normal file

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,331 @@
include <Round-Anything-master/polyround.scad>
$fn = $preview ? 32 : 128;
module counter_bored_holes(screw_offset){
// Subtract mounting holes for stator
for (i = [0:360/number_of_mounting_holes:360]) {
// Subtract screw hole
rotate([0, 0, i])
translate([screw_offset, 0, -1]) // Adjusted position
cylinder(d = screw_diameter, h = stator_wall_thickness + 2);
// Subtract counterbore
rotate([0, 0, i])
translate([screw_offset, 0, stator_wall_thickness - screw_bore_depth]) // Adjusted position
cylinder(d = screw_bore_diameter, h = stator_wall_thickness);
}
}
module rounded_cube(width, length, height, radius1, radius2) {
polyRoundExtrude([
[-(width + radius*2)/2, -(length + radius*2)/2, radius], // Bottom-left point
[-(width + radius*2)/2, (length + radius*2)/2, radius], // Top-left point
[(width + radius*2)/2, (length + radius*2)/2, radius], // Top-right point
[(width + radius*2)/2, -(length + radius*2)/2, radius] // Bottom-right point
], height + radius*2, radius1, radius2);
}
module box() {
padding = radius*2 + inset_distance*2;
difference() {
translate([0, 0, -(height + padding + wall_thickness*2)/2])
rounded_cube(
width + wall_thickness*2 + inset_distance*2,
depth + wall_thickness*2 + inset_distance*2,
height + wall_thickness*2 + inset_distance*2,
radius, radius);
translate([0, 0, -(height + radius*2 + inset_distance*2)/2])
rounded_cube(
width + inset_distance*2,
depth + inset_distance*2,
height + inset_distance*2,
radius, radius);
cube([width, depth, height], center = true);
translate([0, 0, (height + padding)/2 + inset_offset])
rounded_inset(width = width, height = depth, inset_scale=0, hole = true, cutout = false);
rotate([180, 0, 0])
translate([0, 0, (height + padding)/2 + inset_offset])
rounded_inset(width = width, height = depth, cutout = false);
rotate([90, 90, 0])
translate([0, 0, (depth + padding)/2 + inset_offset])
rounded_inset(width = height, height = width, cutout = false);
rotate([-90, 90, 0])
translate([0, 0, (depth + padding)/2 + inset_offset])
rounded_inset(width = height, height = width, cutout = false);
rotate([0, 90, 0])
translate([0, 0, (width + padding)/2 + inset_offset])
rounded_inset(width = height, height = depth, cutout = false);
rotate([0, -90, 0])
translate([0, 0, (width + padding)/2 + inset_offset])
rounded_inset(width = height, height = depth, cutout = false);
/*
translate([(width + padding)/2 + 1.5, (depth + padding)/2 + 1.5, 0])
rotate([0, 0, 45])
dowel();
rotate([180, 0, 0])
translate([(width + padding)/2 + 1.5, (depth + padding)/2 + 1.5, 0])
rotate([0, 0, 45])
dowel();
rotate([0, 0, 90])
translate([(depth + padding)/2 + 1.5, (width + padding)/2 + 1.5, 0])
rotate([0, 0, 45])
dowel();
rotate([0, 180, 90])
translate([(depth + padding)/2 + 1.5, (width + padding)/2 + 1.5, 0])
rotate([0, 0, 45])
dowel();
*/
translate([(width + padding + 3.5)/2, (depth + padding + 3.5)/2, (height + padding)/2 + wall_thickness])
counter_bored_hole();
rotate([0, 0, 180])
translate([(width + padding + 3.5)/2, (depth + padding + 3.5)/2, (height + padding)/2 + wall_thickness])
counter_bored_hole();
rotate([0, 0, 90])
translate([(depth + padding + 3.5)/2, (width + padding + 3.5)/2, (height + padding)/2 + wall_thickness])
counter_bored_hole();
rotate([0, 0, -90])
translate([(depth + padding + 3.5)/2, (width + padding + 3.5)/2, (height + padding)/2 + wall_thickness])
counter_bored_hole();
}
}
module rounded_inset(width, height, inset_scale=0, hole = false, cutout = true) {
difference(){
union(){
translate([0, 0, inset_thickness/2 - 0.75])
rounded_cube(
width + 0.85 + inset_scale*2,
height * 0.75 + inset_scale*2,
1.5 - radius*2, 0.75, 0.75);
//translate([0, 0, -inset_thickness])
rounded_cube(
width + inset_scale*2,
height + inset_scale*2,
inset_thickness - radius*2 + inset_scale, 0, 2);
translate([0, 0, -2.5 + inset_scale])
rounded_cube(
width - radius*2 + inset_scale*2,
height - radius*2 + inset_scale*2,
2.5 - radius*2 - inset_scale, 0, 0);
}
if(cutout)
translate([0, 0, -2.5 + inset_scale])
rounded_cube(
width - radius*2 + inset_scale*2 - 2,
height - radius*2 + inset_scale*2 - 2,
2.5 - radius*2 - inset_scale, 0, 0);
}
if(hole)
translate([width/2 + 1, 0, -100])
rounded_cube(2, 3, 110, 0, 0);
if(hole)
rotate([0, 0, 180])
translate([width/2 + 1, 0, -100])
rounded_cube(2, 3, 110, 0, 0);
}
module dowel(inset_scale=0){
//cube([3 + inset_scale, 3 + inset_scale, 10 + inset_scale*2], center=true);
translate([0, 0, -6])
rounded_cube(-0.5 + inset_scale*3, -0.5 + inset_scale*3, 8 + inset_scale*3, 1, 1);
}
module counter_bored_hole(){
translate([0, 0, -7])
cylinder(d = screw_bore_diameter, h = 7);
translate([0, 0, -20])
cylinder(d = screw_diameter, h = 20);
translate([0, 0, -20])
cylinder(d = screw_insert_diameter, h = 9);
}
module box_bottom() {
difference(){
intersection(){
box();
translate([0, 0, 40])
cube([width + wall_thickness*2 + radius*4 + inset_distance*2,
depth + wall_thickness*2 + radius*4 + inset_distance*2,
height + wall_thickness*2 + radius*4 + inset_distance*2], center = true);
}
}
}
module box_top() {
difference(){
difference(){
box();
translate([0, 0, 40])
cube([width + wall_thickness*2 + radius*4 + inset_distance*2,
depth + wall_thickness*2 + radius*4 + inset_distance*2,
height + wall_thickness*2 + radius*4 + inset_distance*2], center = true);
}
}
}
module front_face() {
difference(){
union(){
rounded_inset(width = height, height = width, inset_scale=inset_scale_factor, hole = false);
translate([0, 15, -7])
cylinder(d = 18, h = 9.2);
}
translate([0, 15, -5])
cylinder(d = 16, h = 7.2);
translate([0, 15, -7])
cylinder(d = 7.5, h = 7.5);
translate([0, -15, 0])
linear_extrude(2.5){
square([17.5, 25.5], center = true);
}
}
}
module back_face() {
difference(){
rounded_inset(width = height, height = width, inset_scale=inset_scale_factor, hole = false);
translate([0, -30, -10])
cylinder(d = 10, h = 20);
translate([0, 30, -10])
cylinder(d = 10, h = 20);
//rotate([0, 0, 90])
translate([0, width/2 - 27.5, 0])
for (i = [0:1:2]) {
translate([0, -10 * i, -1])
rounded_cube(15, -1, 10, 0, 0);
}
}
}
module bottom_face() {
rounded_inset(width = width, height = depth, inset_scale=inset_scale_factor, hole = false);
}
module bottom_face_alt() {
tab_height = 35;
tab_width = 65;
rail_width = 48.5;
difference(){
union(){
rounded_inset(width = width, height = depth, inset_scale=inset_scale_factor, hole = false);
rotate([180, 0, 0])
translate([0, depth/2 - 20, 0])
rounded_cube(tab_width, -1, tab_height, 2, -2);
rotate([180, 0, 0])
translate([0, depth/2 - depth + 15, 0])
rounded_cube(tab_width - 15, -1, tab_height, 2, -2);
}
translate([0, -10, -tab_height + 3])
cube([rail_width, 20, 3.5], center=true);
translate([0, -10, -tab_height + 3 + 26])
cube([rail_width, 20, 3.5], center=true);
translate([-tab_width + 7 + 10, -10, -tab_height + 3 + 13])
cube([rail_width, 20, 13], center=true);
translate([0, 10, -tab_height + 3 + 17])
cube([35, 20, 13], center=true);
//translate([0, 10, -tab_height + 3])
// cube([35, 20, 6], center=true);
}
}
module top_face() {
difference(){
rounded_inset(width = width, height = depth, inset_scale=inset_scale_factor, hole = false);
rotate([0, 0, 90])
translate([depth/2 - 15, width/2 - 12.5, -5])
for (i = [0:1:5]) {
translate([0, -10 * i, -1])
rounded_cube(15, -1, 10, 0, 0);
}
}
}
module side_face() {
rounded_inset(width = height, height = depth, inset_scale=inset_scale_factor, hole = false);
}
width = 75;//90; // Total width
height = 30; // Total height
depth = 60; //110; // Total depth
radius = 2; // Box corner radius
wall_thickness = 7; // Wall thickness
inset_distance = 1; //distance from inset side
inset_offset = 4.5;
inset_thickness = 2.5;
inset_scale_factor = -0.3;
screw_diameter = 3.2; // Diameter of screws - M3 clearance hole (3.2mm diameter)
screw_bore_diameter = 6.5;
screw_insert_diameter = 4.5;
//
difference(){
union(){
//box_top();
//rotate([180, 0, 0])
//half_box();
//translate([0, 0, (height + radius*2 + inset_distance*2)/2 + inset_offset])
//rounded_inset(width = width, height = depth, inset_scale=inset_scale_factor, hole = false);
//padding = radius*2 + inset_distance*2;
//rotate([0, 0, 90])
//translate([(depth + padding)/2 + inset_offset*1.5, (width + padding)/2 + inset_offset*1.5, 0])
//rotate([0, 0, 45])
//dowel(inset_scale=inset_scale_factor);
}
//cube([100, 100, 100]);
}
//padding = radius*2 + inset_distance*2;
//translate([(width + padding + 3)/2 + 5, (depth + padding + 3)/2, (height + padding)/2 + wall_thickness])
// counter_bored_hole();
//rounded_inset(width = height, height = width, cutout = true);
//front_face();
//bottom_face_alt();
side_face();

View file

@ -0,0 +1,491 @@
/* [Siren General Parameters] */
siren_diameter = 125; // Base diameter - 121 for rotor to fit original siren
siren_height = 45; // Height of the rotor - 60 for rotor to fit original siren
number_of_ports = 3; // Number of ports
vertical_tolerance = 2; // Tolerance between stator and rotor
radial_tolerance = 1.5;
fit_reduction = 0.2;
pressure_zone = 0.5; // Percentage of diameter
round_radius = 4; // Rounding of top/bottom
$fn = $preview ? 32 : 128; // Number of fragments
/* [Stator Parameters] */
stator_wall_thickness = 8; // Stator wall thickness
number_of_mounting_holes = 4; // Number of mounting holes
screw_diameter = 3.2; // Diameter of screws - M3 clearance hole (3.2mm diameter)
screw_bore_diameter = 6.5;
screw_insert_diameter = 4.5;
screw_bore_depth = 2.5;
screw_length = 10 - (stator_wall_thickness - screw_bore_depth);
stator_screw_offset = siren_diameter/2 - stator_wall_thickness/2; //distance to screws
/* [Rotor Parameters] */
rotor_wall_thickness = 3; // Rotor wall thickness
hub_height = 3;
hub_diameter = 12;
blade_angle = 8;
rotor_height = siren_height - (vertical_tolerance * 2);
port_height = siren_height - (rotor_wall_thickness * 2) - (vertical_tolerance * 2); // Height of the port
/* [Motor Parameters] */
motor_shaft_diameter = 8; // Diameter of the motor body
motor_frame_diameter = 60;
motor_diameter = 28; // Diameter of the motor body
motor_height = 24;
magnet_diameter = 7;
motor_screw_x_offset = 9.5;
motor_screw_y_offset = 8;
motor_screw_offset = 8;
motor_frame_screw_offset = motor_frame_diameter/2 - stator_wall_thickness/2; //distance to screws
/* [Base Parameters] */
leg_screw_offset = motor_frame_diameter / 2 + 5;
leg_height = motor_height + 10;
// helper functions
module ports(diameter, height, ports, r_offset = 0) {
radius = diameter / 2;
angle = (180 / ports) - (r_offset * 6);
rotate([0, 0, 180 / ports])
for (i = [0:360/ports:360]) {
rotate([0, 0, i + (r_offset * 6 / 2)])
linear_extrude(height)
polygon(concat(
[[0, 0]], // Center point
[for (j = [0 : $fn])
[radius * cos(angle * j / $fn),
radius * sin(angle * j / $fn)]]
));
}
}
module column(outer_diameter, inner_diameter, height, z_offset = 0) {
difference(){
// Outer cylinder
cylinder(d = outer_diameter, h = height);
// Subtract inner cylinder
translate([0, 0, z_offset])
cylinder(d = inner_diameter, h = height - z_offset);
}
}
module rounded_column(outer_diameter, inner_diameter, height, z_offset = 0){
difference() {
// Rounded top
intersection() {
// Unrounded full cylinder
cylinder(d = outer_diameter, h = height);
// Add Minkowski rounded version
minkowski() {
cylinder(h = height - round_radius, d = outer_diameter - round_radius * 2);
sphere(r = round_radius);
}
}
// Subtract inner cylinder
translate([0, 0, z_offset])
cylinder(d = inner_diameter, h = height);
}
}
module counter_bored_holes(screw_offset){
// Subtract mounting holes for stator
for (i = [0:360/number_of_mounting_holes:360]) {
// Subtract screw hole
rotate([0, 0, i])
translate([screw_offset, 0, -1]) // Adjusted position
cylinder(d = screw_diameter, h = stator_wall_thickness + 2);
// Subtract counterbore
rotate([0, 0, i])
translate([screw_offset, 0, stator_wall_thickness - screw_bore_depth]) // Adjusted position
cylinder(d = screw_bore_diameter, h = stator_wall_thickness);
}
}
module mounting_holes(screw_offset){
for (i = [0:360/number_of_mounting_holes:360]) {
rotate([0, 0, i])
translate([screw_offset, 0, 0])
cylinder(d = screw_insert_diameter, h = screw_length);
}
}
// Linear Interpolation
function lerp(a, b, t) = a * (1 - t) + b * t;
// Arc between points function
function arc_between_points(p1, p2, height, steps=20) =
[for(t = [0:1/steps:1])
let(
x = lerp(p1.x, p2.x, t),
y = lerp(p1.y, p2.y, t),
// Parabolic arc height calculation
arc_height = height * (1 - pow(2*t-1, 2))
)
[x, y + arc_height]
];
function bezier_curve(t, p0, p1, p2, p3, p4) =
pow(1-t, 4) * p0 +
4 * pow(1-t, 3) * t * p1 +
6 * pow(1-t, 2) * pow(t, 2) * p2 +
4 * (1-t) * pow(t, 3) * p3 +
pow(t, 4) * p4;
function curved_polygon(points, steps, x_shift, y_shift) =
let(
left_curve = [for (t = [0 : 1/steps : 1])
bezier_curve(t, points[0], points[1], points[2], points[3], points[4])
],
right_curve = [for (pt = left_curve)
[pt[0] + x_shift, pt[1] + y_shift]
]
)
concat(left_curve, [for (i = [len(right_curve)-1 : -1 : 0]) right_curve[i]]);
module stator() {
difference() {
// Create column
column(siren_diameter, siren_diameter - (stator_wall_thickness * 2), siren_height);
// Subtract ports
translate([0, 0, (siren_height - port_height) / 2])
ports(siren_diameter, port_height, number_of_ports);
mounting_holes(stator_screw_offset);
translate([0, 0, siren_height - screw_length])
mounting_holes(stator_screw_offset);
}
}
module impeller_blade(radius, rotor_height){
// Generate points
points1 = arc_between_points([0, -hub_diameter / 2], [radius, 0], -blade_angle);
points2 = [for(p = [len(points1)-1:-1:0]) points1[p] - [0, -rotor_wall_thickness]];
// Create the blade by combining top and bottom points
linear_extrude(height = rotor_height)
polygon(concat(points1, points2));
}
module impeller_blades_curved(radius, rotor_height){
difference() {
union() {
// Rotate and create blades
for (i = [0:360/number_of_ports:360]) {
rotate([0, 0, i])
impeller_blade(radius, rotor_height);
}
}
top_rounding_radius = 5;
bottom_rounding_radius = rotor_height / 2 - 1;
//bottom_rounding_radius = rotor_height / 2 - 10; // this needs to be adjusted based on the rotor diameter and height
top_diameter = (siren_diameter * pressure_zone) + (top_rounding_radius * 2);
bottom_diameter = (siren_diameter * pressure_zone) - (2 * bottom_rounding_radius);
top_height = top_rounding_radius + 10;
bottom_height = rotor_height - bottom_rounding_radius - (top_rounding_radius * 2) + 2;
translate([0, 0, rotor_height - top_rounding_radius])
union(){
difference() {
// Column
cylinder(d = top_diameter, h = top_height);
// Top torus
rotate_extrude()
translate([top_diameter / 2, 0, 0])
circle(r = top_rounding_radius);
}
rotate([180, 0, 0])
minkowski() {
// Original cylinder, reduced by twice the rounding radius
cylinder(d = bottom_diameter, h = bottom_height);
// Sphere for rounding
sphere(r = bottom_rounding_radius);
}
}
}
}
module rotor() {
rotor_diameter = siren_diameter - (stator_wall_thickness * 2) - (radial_tolerance * 2);
rotor_inner_diameter = rotor_diameter - (rotor_wall_thickness * 2);
difference() {
union(){
difference() {
column(rotor_diameter, rotor_inner_diameter, rotor_height, rotor_wall_thickness);
// Subtract ports
translate([0, 0, (rotor_height - port_height) / 2])
ports(rotor_diameter, port_height, number_of_ports);
}
// Add blades
impeller_blades_curved(rotor_inner_diameter / 2, rotor_height);
}
translate([0, 0, 0])
hub_attachment(fit_reduction);
}
}
module hub(aug = 0) {
height = (rotor_wall_thickness * 2) + hub_height;
difference(){
union(){
hub_attachment(aug);
cylinder(d = motor_shaft_diameter + 2, h = height);
}
intersection(){
cylinder(d = motor_shaft_diameter, h = height);
cube([motor_shaft_diameter, motor_shaft_diameter-1, height * 2], center = true);
};
cylinder(d = motor_shaft_diameter, h = 2);
}
}
module hub_attachment(aug = 0) {
insert_width = 2;
outer_diameter = hub_diameter + (insert_width * 6) + (aug * 2);
mid_diameter = hub_diameter + (insert_width * 2) + (aug * 2);
inner_diameter = outer_diameter - (insert_width * 2) - (aug * 4);
difference(){
column(outer_diameter, inner_diameter, rotor_wall_thickness);
//translate([0, 0, -10])
ports(outer_diameter, rotor_wall_thickness, number_of_ports, aug * 2);
}
translate([0, 0, rotor_wall_thickness])
cylinder(d = outer_diameter, rotor_wall_thickness);
cylinder(d = mid_diameter, rotor_wall_thickness * 2);
}
module stator_top() {
difference(){
rounded_column(
siren_diameter,
siren_diameter * pressure_zone,
stator_wall_thickness
);
counter_bored_holes(stator_screw_offset);
}
}
module stator_bottom() {
//rotate([180, 0, 0])
difference() {
rounded_column(
siren_diameter,
motor_diameter + (radial_tolerance * 2),
stator_wall_thickness
);
counter_bored_holes(stator_screw_offset);
// Subtract mounting holes for motor frame
rotate([180, 0, 0])
translate([0, 0, -stator_wall_thickness])
counter_bored_holes(motor_frame_screw_offset);
rotate([180, 0, 0])
translate([0, 0, -stator_wall_thickness])
counter_bored_holes(leg_screw_offset);
translate([0, 0, stator_wall_thickness])
rotate([0, 0, -45/2 - 0.6])
for (i = [0:360/number_of_mounting_holes:360]) {
rotate([0, 0, i]) {
leg(fit_reduction);
}
}
}
}
module motor_frame() {
//rotate([180, 0, 0])
//translate([0, 0, -stator_wall_thickness])
motor_frame_height = motor_height - stator_wall_thickness - vertical_tolerance;
difference(){
union(){
inner_diameter = motor_frame_diameter - (stator_wall_thickness * 2);
difference(){
rounded_column(
motor_frame_diameter,
inner_diameter,
motor_frame_height + stator_wall_thickness,
-stator_wall_thickness
);
translate([0, 0, stator_wall_thickness])
ports(inner_diameter, motor_frame_height, number_of_mounting_holes);
//translate([0, 0, -stator_wall_thickness])
//rotate([0, 0, -45/2])
ports(motor_frame_diameter, motor_frame_height, number_of_mounting_holes);
rotate([0, 0, 45/2])
mounting_holes(motor_frame_screw_offset);
}
translate([0, 0, motor_frame_height])
column(motor_screw_x_offset * 2 + (radial_tolerance * 2) , magnet_diameter + (radial_tolerance * 2), stator_wall_thickness);
}
// Subtract mounting holes for motor
translate([0, 0, motor_frame_height])
rotate([0, 0, 45/2])
for (i = [0:360/number_of_mounting_holes:360]) {
rotate([0, 0, i]) {
// Use modulo to alternate between x and y offsets
motor_screw_offset = (floor(i / (360/number_of_mounting_holes)) % 2 == 0) ?
motor_screw_x_offset : motor_screw_y_offset;
translate([motor_screw_offset, 0, 0])
cylinder(d = screw_diameter, h = stator_wall_thickness);
}
}
translate([0, 0, motor_frame_height])
cylinder(d = magnet_diameter + (radial_tolerance * 2), h = stator_wall_thickness);
}
}
module leg(aug = 0) {
motor_spacing = 15;
points = [
[leg_screw_offset + 10, leg_height],
[leg_screw_offset + 5, leg_height-5],
[leg_screw_offset + 0, leg_height / 2],
[leg_screw_offset + 5, 5],
[leg_screw_offset + 10, 0]
];
rotate_extrude(angle = 360/8) {
polygon(curved_polygon(
points,
steps = 100,
x_shift = -stator_wall_thickness / 2,
y_shift = 0
));
}
rotate([0, 0, 12 - (aug / 2)])
translate([0, 0, -1])
difference(){
rotate_extrude(angle = 360/16 + aug) {
translate([leg_screw_offset - ((screw_insert_diameter + radial_tolerance) / 2) - aug, 0, 0])
square([screw_insert_diameter + radial_tolerance + aug * 2, leg_height + 2]);
}
if(aug == 0){
rotate([0, 0, 45/4 - aug])
//translate([0, 0, -2])
mounting_holes(leg_screw_offset);
rotate([0, 0, 45/4 - aug])
translate([0, 0, leg_height - screw_length + 2])
mounting_holes(leg_screw_offset);
}
}
}
module base() {
difference(){
rounded_column(
siren_diameter,
(leg_screw_offset - ((screw_diameter + radial_tolerance) / 2)) * 2 - 5,
stator_wall_thickness
);
counter_bored_holes(stator_screw_offset);
translate([0, 0, stator_wall_thickness])
rotate([180, 0, 0])
//counter_bored_holes(leg_screw_offset - ((screw_diameter + tolerance) / 2));
//rotate([0, 0, -45/2])
counter_bored_holes(leg_screw_offset);
translate([0, 0, stator_wall_thickness])
rotate([0, 0, -45/2 - 0.6])
for (i = [0:360/number_of_mounting_holes:360]) {
rotate([0, 0, i]) {
leg(fit_reduction);
}
}
}
}
//rotate([0, 0, 0])
//translate([0, 0, 0])
//rotor();
//motor_shaft_diameter = 8.2;
hub(0.1);
//stator();
//base();
//stator_bottom();
//rotate([0, 0, 0])
//translate([siren_diameter, 0, vertical_tolerance])
//rotor();
//stator_bottom();
//rotate([0, 0, 67.5])
//motor_frame();
//rotate([0, 0, 0])
//translate([0, 0, siren_height + 2])
//stator_top();
//rotate([180, 0, 0])
//translate([0, 0, 2])
//stator_bottom();
//rotate([180, 0, 45])
//translate([0, 0, stator_wall_thickness + 5])
//motor_frame();
//leg();
//base();
/*
rotate([0, 0, 0])
translate([0, 0, -leg_height - 15 - stator_wall_thickness - 2])
base();
rotate([0, 0, -45/2])
translate([0, 0, -leg_height - 15])
for (i = [0:360/number_of_mounting_holes:360]) {
rotate([0, 0, i]) {
leg();
}
}
*/
/*
//translate([0, 0, 2])
intersection(){
//color("red")
rotor();
cylinder(d = 30, h = 10);
}
translate([040, 0, 0])
hub();
*/

View file

@ -0,0 +1,292 @@
/* [Siren General Parameters] */
siren_diameter = 121; // Base diameter - 121 for rotor to fit original siren
siren_height = 60; // Height of the rotor - 60 for rotor to fit original siren
number_of_ports = 6; // Number of ports
tolerance = 2; // Tolerance between stator and rotor
pressure_zone = 0.5; // Percentage of diameter
round_radius = 4; // Rounding of top/bottom
$fn = $preview ? 32 : 128; // Number of fragments
/* [Stator Parameters] */
stator_wall_thickness = 8; // Stator wall thickness
number_of_mounting_holes = 4; // Number of mounting holes
screw_diameter = 3.2; // Diameter of screws - M3 clearance hole (3.2mm diameter)
screw_bore_diameter = 6.5;
screw_insert_diameter = 4.5;
screw_bore_depth = 2.5;
screw_length = 10 - (stator_wall_thickness - screw_bore_depth);
stator_screw_offset = siren_diameter/2 - stator_wall_thickness/2; //distance to screws
/* [Rotor Parameters] */
rotor_wall_thickness = 3; // Rotor wall thickness
hub_height = 3;
hub_diameter = 12;
blade_angle = 8;
port_height = siren_height - (rotor_wall_thickness * 2) - (tolerance * 2); // Height of the port
/* [Motor Parameters] */
motor_shaft_diameter = 8; // Diameter of the motor body
motor_frame_diameter = 60;
motor_diameter = 28; // Diameter of the motor body
motor_height = 24;
magnet_diameter = 7;
motor_screw_x_offset = 9.5;
motor_screw_y_offset = 8;
motor_screw_offset = 8;
motor_frame_screw_offset = motor_frame_diameter/2 - stator_wall_thickness/2; //distance to screws
/* [Base Parameters] */
leg_screw_offset = motor_frame_diameter / 2 + 5;
leg_height = motor_height + 10;
/* [Iris Parameters] */
iris_blade_arc_angle = 135;
number_of_iris_blades = 8;
// helper functions
module ports(diameter, height, ports, r_offset = 0) {
radius = diameter / 2;
angle = (180 / ports) - (r_offset * 6);
rotate([0, 0, 180 / ports])
for (i = [0:360/ports:360]) {
rotate([0, 0, i + (r_offset * 6 / 2)])
linear_extrude(height)
polygon(concat(
[[0, 0]], // Center point
[for (j = [0 : $fn])
[radius * cos(angle * j / $fn),
radius * sin(angle * j / $fn)]]
));
}
}
module column(outer_diameter, inner_diameter, height, z_offset = 0) {
difference(){
// Outer cylinder
cylinder(d = outer_diameter, h = height);
// Subtract inner cylinder
translate([0, 0, z_offset])
cylinder(d = inner_diameter, h = height - z_offset);
}
}
module rounded_column(outer_diameter, inner_diameter, height, z_offset = 0){
difference() {
// Rounded top
intersection() {
// Unrounded full cylinder
cylinder(d = outer_diameter, h = height);
// Add Minkowski rounded version
minkowski() {
cylinder(h = height - round_radius, d = outer_diameter - round_radius * 2);
sphere(r = round_radius);
}
}
// Subtract inner cylinder
translate([0, 0, z_offset])
cylinder(d = inner_diameter, h = height);
}
}
module counter_bored_holes(screw_offset){
// Subtract mounting holes for stator
for (i = [0:360/number_of_mounting_holes:360]) {
// Subtract screw hole
rotate([0, 0, i])
translate([screw_offset, 0, -1]) // Adjusted position
cylinder(d = screw_diameter, h = stator_wall_thickness + 2);
// Subtract counterbore
rotate([0, 0, i])
translate([screw_offset, 0, stator_wall_thickness - screw_bore_depth]) // Adjusted position
cylinder(d = screw_bore_diameter, h = stator_wall_thickness);
}
}
module mounting_holes(screw_offset){
for (i = [0:360/number_of_mounting_holes:360]) {
rotate([0, 0, i])
translate([screw_offset, 0, 0])
cylinder(d = screw_insert_diameter, h = screw_length);
}
}
// Linear Interpolation
function lerp(a, b, t) = a * (1 - t) + b * t;
// Arc between points function
function arc_between_points(p1, p2, height, steps=20) =
[for(t = [0:1/steps:1])
let(
x = lerp(p1.x, p2.x, t),
y = lerp(p1.y, p2.y, t),
// Parabolic arc height calculation
arc_height = height * (1 - pow(2*t-1, 2))
)
[x, y + arc_height]
];
function bezier_curve(t, p0, p1, p2, p3, p4) =
pow(1-t, 4) * p0 +
4 * pow(1-t, 3) * t * p1 +
6 * pow(1-t, 2) * pow(t, 2) * p2 +
4 * (1-t) * pow(t, 3) * p3 +
pow(t, 4) * p4;
function curved_polygon(points, steps, x_shift, y_shift) =
let(
left_curve = [for (t = [0 : 1/steps : 1])
bezier_curve(t, points[0], points[1], points[2], points[3], points[4])
],
right_curve = [for (pt = left_curve)
[pt[0] + x_shift, pt[1] + y_shift]
]
)
concat(left_curve, [for (i = [len(right_curve)-1 : -1 : 0]) right_curve[i]]);
module stator_top() {
difference(){
rounded_column(
siren_diameter,
siren_diameter * pressure_zone,
stator_wall_thickness
);
counter_bored_holes(stator_screw_offset);
}
}
module iris_top() {
outer_diameter = stator_screw_offset * 2 - 5;
inner_diameter = siren_diameter * pressure_zone + 5;
blade_height = 0.5;
center = (inner_diameter / 2) + ((outer_diameter - inner_diameter) / 4);
difference(){
column(
outer_diameter,
inner_diameter,
3
);
translate([0, 0, 2])
linear_extrude(){
for (i = [0:360/8:360]) {
rotate([0, 0, i])
translate([center, 0, 0])
rotate([0, 0, 90])
square([5, 25], center = true);
}
}
}
column(outer_diameter + 0.5, outer_diameter - 0.5, 5);
}
module iris_bottom() {
outer_diameter = stator_screw_offset * 2 - 5;
inner_diameter = siren_diameter * pressure_zone + 5;
//blade_height = 0.5;
center = (inner_diameter / 2) + ((outer_diameter - inner_diameter) / 4);
difference(){
column(
siren_diameter,
siren_diameter * pressure_zone,
3
);
translate([0, 0, 2])
linear_extrude(){
for (i = [0:360/8:360]) {
rotate([0, 0, i])
translate([center, 0, 0])
circle(2.5);
}
}
counter_bored_holes(stator_screw_offset);
translate([0, 0, 1])
column(outer_diameter + 1, outer_diameter - 1, 10);
}
}
module iris_blade() {
outer_diameter = stator_screw_offset * 2 - 10;
inner_diameter = siren_diameter * pressure_zone + 10;
blade_height = 0.5;
center = (inner_diameter / 2) + ((outer_diameter - inner_diameter) / 4);
linear_extrude(blade_height){
difference(){
for (i = [0:iris_blade_arc_angle/$fn:iris_blade_arc_angle]) {
rotate([0, 0, i])
translate([center, 0, 0])
circle((outer_diameter - inner_diameter) / 4);
}
translate([center, 0, 0])
circle(2.5);
rotate([0, 0, iris_blade_arc_angle])
translate([center, 0, 0])
circle(2.5);
}
}
}
//color("blue")
iris_bottom();
translate([0, 0, 2])
color("red")
iris_blade();
translate([0, 0, 10])
iris_top();
/*
iris_blade_arc_angle = 135;
number_of_iris_blades = 8;
outer_diameter = stator_screw_offset * 2 - tolerance;
inner_diameter = siren_diameter * pressure_zone + tolerance;
center = (inner_diameter / 2) + ((outer_diameter - inner_diameter) / 4);
rotate([0, 0, 0])
color("blue")
square([5, 200]);
//iris_top();
rotate([0, 0, -45])
for(i = [0:360/8:360/8 * 0]){
//this point is the radial translation of the rh pin based on angle of the blade
pin_angle = -70;
echo(center * cos(pin_angle), center * sin(pin_angle));
//echo((center * cos(pin_angle)) + pin_angle);
//rotate([0, 0, -10])
rotate([0, 0, i])
translate([(center + 0) * cos(pin_angle), (center + 0) * sin(pin_angle), i/50])
rotate([0, 0, -35])
//rotate([0, 0, -0 + (pin_angle / ((iris_blade_arc_angle / -(pin_angle)) + 1))])
translate([-center, 0, 0])
color("red")
iris_blade();
}
translate([0, 0, -10])
circle(d = center * 2);
*/

View file

@ -0,0 +1,36 @@
function bezier_curve(t, p0, p1, p2, p3, p4) =
pow(1-t, 4) * p0 +
4 * pow(1-t, 3) * t * p1 +
6 * pow(1-t, 2) * pow(t, 2) * p2 +
4 * (1-t) * pow(t, 3) * p3 +
pow(t, 4) * p4;
function curved_polygon(points, steps, x_shift, y_shift) =
let(
left_curve = [for (t = [0 : 1/steps : 1])
bezier_curve(t, points[0], points[1], points[2], points[3], points[4])
],
right_curve = [for (pt = left_curve)
[pt[0] + x_shift, pt[1] + y_shift]
]
)
concat(left_curve, [for (i = [len(right_curve)-1 : -1 : 0]) right_curve[i]]);
module base() {
rotate_extrude() {
polygon(curved_polygon(
points = [
[-80, 30],
[-50, 15],
[-50, 0],
[-50, -15],
[-80, -30]
],
steps = 100,
x_shift = 50,
y_shift = 0
));
}
}
base();

View file

@ -0,0 +1,79 @@
// Blade Dimensions
diameter = 100; // Base diameter
height = 100; // Height of the rotor
wall_thickness = 6; // Wall thickness
tolerance = 2; // Tolerance between stator and rotor
number_of_ports = 6; // Number of ports [2:8]
port_height = 50; // Height of the port
pressure_zone = 0.4; // Define this globally with an appropriate default value
mounting_hole_diameter = 4; // Diameter of mounting holes
mounting_hole_offset = 20; // Radial distance from center to hole centers
number_of_mounting_holes = 4; // Number of mounting holes
mounting_hole_bore = 8; // Diameter of mounting holes counterbore
round_radius = 4;
motor_shaft_diameter = 5;
hub_height = 10;
motor_width = 20; // Diameter of the motor shaft
$fn = 128; // Number of fragments
diameter = 100;
blade_width = 5;
blade_length = diameter;
// Blade Height
blade_height = 10;
// Number of ports/blades
number_of_ports = 5;
// Pressure zone percentage
pressure_zone = 0.4; // 30% of diameter
module impeller_blades() {
difference() {
intersection() {
// Column with full diameter
cylinder(d = diameter, h = blade_height);
// Blades
union() {
//rotate([180, 180, 180])
for (i = [0:360/number_of_ports:360]) {
rotate([0, 0, i - 90])
translate([0, diameter / 2, 0])
rotate([0, 0, 10])
translate([-blade_width, -diameter / 2, 0])
linear_extrude(height = blade_height, twist = 0, slices=360)
square([blade_width, blade_length]);
}
}
}
// Subtract core cylinder
translate([0, 0, -1])
cylinder(d = diameter * pressure_zone, h = blade_height + 2);
}
}
module ports() {
radius = diameter / 2;
angle = 360 / number_of_ports / 2;
rotate([0, 0, 180 / number_of_ports])
for (i = [0:360/number_of_ports:360]) {
rotate([0, 0, i])
translate([0, 0, (height - port_height) / 2])
linear_extrude(height = port_height)
polygon(concat(
[[0, 0]], // Center point
[for (j = [0 : $fn])
[radius * cos(angle * j / $fn),
radius * sin(angle * j / $fn)]]
));
}
}
// Optional: If you want to render the blades independently for testing
impeller_blades();
ports();

View file

@ -0,0 +1,166 @@
include <Round-Anything-master/polyround.scad>
$fn = $preview ? 32 : 64;
module rounded_rectangle(width, length, height, radius1, radius2) {
polyRoundExtrude([
[-width/2, -length/2, 1], // Bottom-left point
[-width/2, length/2, 1], // Top-left point
[width/2, length/2, 1], // Top-right point
[width/2, -length/2, 1] // Bottom-right point
], height, radius1, radius2);
}
module box() {
difference() {
// Outer rounded box
minkowski() {
cube([width + wall_thickness*2 + inset_distance*2,
depth + wall_thickness*2 + inset_distance*2,
height + wall_thickness*2 + inset_distance*2], center = true);
sphere(r = radius);
}
// Inner space (centered)
minkowski() {
cube([width + inset_distance*2,
depth + inset_distance*2,
height + inset_distance*2], center = true);
sphere(r = radius);
}
/*cube([width,
depth,
height], center = true);
*/
translate([0, 0, height/2 + radius + wall_thickness + inset_distance])
inset(width = width, height = depth);
rotate([180, 0, 0])
translate([0, 0, height/2 + radius + wall_thickness + inset_distance])
inset(width = width, height = depth);
rotate([90, 0, 0])
translate([0, 0, depth/2 + radius + wall_thickness + inset_distance])
inset(width = width, height = height);
rotate([-90, 0, 0])
translate([0, 0, depth/2 + radius + wall_thickness + inset_distance])
inset(width = width, height = height );
rotate([0, 90, 0])
translate([0, 0, width/2 + radius + wall_thickness + inset_distance])
inset(width = height, height = depth);
rotate([0, -90, 0])
translate([0, 0, width/2 + radius + wall_thickness + inset_distance])
inset(width = height, height = depth);
translate([0, 0, (height + inset_distance*2) / 2 + 1])
minkowski() {
cube([width + wall_thickness*2 + inset_distance*2 + 10, 4, 1], center = true);
sphere(r = 0.5);
}
}
}
module inset(width, height) {
translate([0, 0, -inset_inner_thickness - inset_outer_thickness])
linear_extrude(height = inset_inner_thickness) {
minkowski() {
square([
width - (2 * hole_inset_distance),
height - (2 * hole_inset_distance)
], center = true);
circle(r = radius);
}
}
translate([0, 0, -inset_outer_thickness])
linear_extrude(height = inset_outer_thickness) {
minkowski() {
square([width, height], center = true);
circle(r = radius);
}
}
}
module rounded_inset(width, height) {
/*(translate([0, 0, -inset_inner_thickness - inset_outer_thickness])
linear_extrude(height = inset_inner_thickness) {
minkowski() {
square([
width - (2 * hole_inset_distance),
height - (2 * hole_inset_distance)
], center = true);
circle(r = radius);
}
}*/
translate([0, 0, -inset_inner_thickness*2 - inset_outer_thickness])
rounded_rectangle(
width - (2 * hole_inset_distance),
height - (2 * hole_inset_distance),
inset_inner_thickness*2, -2, -2);
translate([0, 0, -inset_outer_thickness])
linear_extrude(height = inset_outer_thickness) {
minkowski() {
square([width, height], center = true);
circle(r = radius);
}
}
}
module snap(){
}
module half_box() {
difference(){
intersection(){
box();
translate([0, 0, (height + wall_thickness*2 + radius*2 + inset_distance*2) / 2])
cube([width + wall_thickness*2 + radius*2 + inset_distance*2,
depth + wall_thickness*2 + radius*2 + inset_distance*2,
height + wall_thickness*2 + radius*2 + inset_distance*2], center = true);
}
/*
minkowski() {
cube([width+ inset_inner_thickness*2,
depth+ inset_inner_thickness*2,
3], center = true);
sphere(r = radius);
}
*/
}
}
// Example usage
width = 110; // Total width
height = 40; // Total height
depth = 110; // Total depth
radius = 3; // Box corner radius
wall_thickness = 3; // Wall thickness
inset_distance = 4; //distance from inset side
hole_inset_distance = 2; //distance from inset side
hole_radius = 3; // Hole corner radius
inset_inner_thickness = 1.7;
inset_outer_thickness = 1.5;
//
half_box();
//rounded_inset(width, height);

View file

@ -0,0 +1,153 @@
// Impeller Parameters
diameter = 100; // Overall base diameter
hub_diameter = 30; // Diameter of the central hub
hub_height = 10; // Height of the central hub
base_thickness = 3; // Thickness of the base
shaft_diameter = 10; // Diameter of the central shaft hole
blade_count = 6; // Number of blades
blade_length = 35; // Length of blades from hub
blade_thickness = 3; // Thickness of blades
blade_height_at_hub = 5; // Blade height at the hub
blade_height_at_diameter = 20; // Blade height at full diameter
module impeller_base() {
// Base with hub
difference() {
union() {
// Solid base disk
cylinder(h = base_thickness, d = diameter, center = false);
// Central hub
translate([0, 0, base_thickness])
cylinder(h = hub_height, d = hub_diameter, center = false);
}
// Countersink for shaft entry
translate([0, 0, -0.1])
cylinder(h = 1, d1 = shaft_diameter, d2 = shaft_diameter + 2, center = false);
// Through hole for shaft
translate([0, 0, base_thickness - 0.1])
cylinder(h = hub_height + 0.2, d = shaft_diameter, center = false);
}
}
module impeller_blade() {
// Blade with base always touching the impeller base
polyhedron(
points = [
// Bottom points at hub
[hub_diameter/2, -blade_thickness/2, base_thickness],
[hub_diameter/2, blade_thickness/2, base_thickness],
// Bottom points at full diameter
[diameter/2, -blade_thickness/2, base_thickness],
[diameter/2, blade_thickness/2, base_thickness],
// Top points at hub
[hub_diameter/2, -blade_thickness/2, base_thickness + blade_height_at_hub],
[hub_diameter/2, blade_thickness/2, base_thickness + blade_height_at_hub],
// Top points at full diameter
[diameter/2, -blade_thickness/2, base_thickness + blade_height_at_diameter],
[diameter/2, blade_thickness/2, base_thickness + blade_height_at_diameter]
],
faces = [
[0, 2, 3, 1], // Bottom face
[0, 1, 5, 4], // Inner side bottom
[2, 6, 7, 3], // Outer side bottom
[4, 5, 7, 6], // Top face
[0, 4, 6, 2], // Side face 1
[1, 3, 7, 5] // Side face 2
]
);
}
module impeller_blade() {
polyhedron(
points = [
// Bottom points at hub - SHIFTED OUTWARD
[hub_diameter/2 + base_thickness * tan(30), -blade_thickness/2, base_thickness],
[hub_diameter/2 + base_thickness * tan(30), blade_thickness/2, base_thickness],
// Bottom points at full diameter - SHIFTED OUTWARD
[diameter/2 + base_thickness * tan(30), -blade_thickness/2, base_thickness],
[diameter/2 + base_thickness * tan(30), blade_thickness/2, base_thickness],
// Top points at hub - INCREASED RADIAL ANGLE
[hub_diameter/2 + blade_height_at_hub * tan(30), -blade_thickness/2, base_thickness + blade_height_at_hub],
[hub_diameter/2 + blade_height_at_hub * tan(30), blade_thickness/2, base_thickness + blade_height_at_hub],
// Top points at full diameter - INCREASED RADIAL ANGLE
[diameter/2 + blade_height_at_diameter * tan(30), -blade_thickness/2, base_thickness + blade_height_at_diameter],
[diameter/2 + blade_height_at_diameter * tan(30), blade_thickness/2, base_thickness + blade_height_at_diameter]
],
faces = [
[0, 2, 3, 1], // Bottom face
[0, 1, 5, 4], // Inner side bottom
[2, 6, 7, 3], // Outer side bottom
[4, 5, 7, 6], // Top face
[0, 4, 6, 2], // Side face 1
[1, 3, 7, 5] // Side face 2
]
);
}
module impeller_blades() {
// Add blades
for (i = [0:360/blade_count:359]) {
rotate([0, 0, i]) {
impeller_blade();
}
}
}
module impeller_column() {
// Column Parameters
column_height = blade_height_at_diameter + base_thickness; // Height matches blade height
num_ports = blade_count; // Number of ports matching blade count
wall_thickness = 5; // Thickness of the column wall
outer_diameter = diameter; // Diameter of the impeller base
// Calculate port width based on equal arc length
port_width = (PI * outer_diameter) / (num_ports * 2);
// Calculate inner diameter based on wall thickness
inner_diameter = outer_diameter - (2 * wall_thickness);
// Rotate by half the port width
rotate([0, 0, 180/(num_ports * 2)])
difference() {
union() {
// Main cylindrical body
cylinder(h = column_height, d = outer_diameter, $fn = 100);
// Close the bottom with wall_thickness height
cylinder(h = wall_thickness, d = outer_diameter, $fn = 100);
}
// Hollow out the center (open on top end)
translate([0, 0, wall_thickness])
cylinder(h = column_height + 1, d = inner_diameter, $fn = 100);
// Create evenly spaced rectangular ports
for (i = [0 : num_ports - 1]) {
rotate([0, 0, i * (360 / num_ports)])
translate([outer_diameter/2 - wall_thickness * 2, -port_width/2, 0])
cube([wall_thickness * 2 + 1, port_width, column_height + 10]);
}
}
}
module complete_impeller() {
// Combine base and blades
impeller_base();
impeller_blades();
//impeller_column();
}
// Render the complete impeller
complete_impeller();

View file

@ -0,0 +1,39 @@
module torus(major_radius, minor_radius, $fn=10) {
rotate_extrude(convexity = 10)
translate([major_radius, 0, 0])
circle(r = minor_radius);
}
module rounded_cube() {
minkowski() {
union(){
sphere(r=2, $fn=10);
/*
difference(){
rotate([180, 0, 0])
cylinder(d = 10, h = 3, $fn=10);
torus(major_radius = 5, minor_radius = 3);
}
*/
}
cube([10, 10, 10]); // Base shape
}
}
rounded_cube();
// Example usage
/*
union(){
sphere(r=2, $fn=100);
difference(){
rotate([180, 0, 0])
cylinder(d = 10, h = 3, $fn=100);
torus(major_radius = 5, minor_radius = 3);
}
}
*/

View file

@ -0,0 +1,31 @@
include <Round-Anything-master/polyround.scad>
module rounded_rectangle(width, length, height, radius1, radius2) {
polyRoundExtrude([
[-width/2, -length/2, 1], // Bottom-left point
[-width/2, length/2, 1], // Top-left point
[width/2, length/2, 1], // Top-right point
[width/2, -length/2, 1] // Bottom-right point
], height, radius1, radius2);
}
/*
radiiPoints=[
[0, 0, 0.5],
[0, 5, 0.5],
[5, 0, 0.5],
[2, 0, 0.5],
[2, -3, 1],
[4, -3, 0],
[4, -5, 0],
[-2, -5, 0],
[-2, -3, 0],
[0, -3, 1]];
extrudeWithRadius(3,1,1,50)
polygon(polyRound(radiiPoints,30));
*/
// Usage examples
rounded_rectangle(10, 10, 4, -2, -2);
//extrudeWithRadius(5,-2,-2,50)square([5, 2]);

View file

@ -0,0 +1,553 @@
/* [Siren General Parameters] */
siren_diameter = 125; // Base diameter
siren_height = 50; // Height of the rotor
number_of_ports = 5; // Number of ports
tolerance = 2; // Tolerance between stator and rotor
pressure_zone = 0.4; // Percentage of diameter
round_radius = 4; // Rounding of top/bottom
$fn = $preview ? 32 : 128; // Number of fragments
/* [Stator Parameters] */
stator_wall_thickness = 8; // Stator wall thickness
number_of_mounting_holes = 4; // Number of mounting holes
screw_diameter = 3.2; // Diameter of screws - M3 clearance hole (3.2mm diameter)
screw_bore_diameter = 6.5;
screw_insert_diameter = 4;
screw_bore_depth = 3;
screw_length = 10 - (stator_wall_thickness - screw_bore_depth);
stator_screw_offset = siren_diameter/2 - stator_wall_thickness/2; //distance to screws
/* [Rotor Parameters] */
rotor_wall_thickness = 3; // Rotor wall thickness
hub_height = 5;
hub_diameter = 12;
blade_angle = 15;
port_height = siren_height - (rotor_wall_thickness * 2) - (tolerance * 2); // Height of the port
/* [Motor Parameters] */
motor_shaft_diameter = 8; // Diameter of the motor body
motor_frame_diameter = 60;
motor_diameter = 28; // Diameter of the motor body
motor_height = 45;
magnet_diameter = 7;
motor_screw_x_offset = 9.5;
motor_screw_y_offset = 8;
motor_screw_offset = 8;
motor_frame_screw_offset = motor_frame_diameter/2 - stator_wall_thickness/2; //distance to screws
// helper functions
module ports(diameter, height, ports) {
radius = diameter / 2;
angle = 180 / ports;
rotate([0, 0, 180 / ports])
for (i = [0:360/ports:360]) {
rotate([0, 0, i])
linear_extrude(height)
polygon(concat(
[[0, 0]], // Center point
[for (j = [0 : $fn])
[radius * cos(angle * j / $fn),
radius * sin(angle * j / $fn)]]
));
}
}
module column(outer_diameter, inner_diameter, height) {
difference(){
// Outer cylinder
cylinder(d = outer_diameter, h = height);
// Subtract inner cylinder
cylinder(d = inner_diameter, h = height);
}
}
// Linear Interpolation
function lerp(a, b, t) = a * (1 - t) + b * t;
// Arc between points function
function arc_between_points(p1, p2, height, steps=20) =
[for(t = [0:1/steps:1])
let(
x = lerp(p1.x, p2.x, t),
y = lerp(p1.y, p2.y, t),
// Parabolic arc height calculation
arc_height = height * (1 - pow(2*t-1, 2))
)
[x, y + arc_height]
];
function bezier_curve(t, p0, p1, p2, p3, p4) =
pow(1-t, 4) * p0 +
4 * pow(1-t, 3) * t * p1 +
6 * pow(1-t, 2) * pow(t, 2) * p2 +
4 * (1-t) * pow(t, 3) * p3 +
pow(t, 4) * p4;
function curved_polygon(points, steps, x_shift, y_shift) =
let(
left_curve = [for (t = [0 : 1/steps : 1])
bezier_curve(t, points[0], points[1], points[2], points[3], points[4])
],
right_curve = [for (pt = left_curve)
[pt[0] + x_shift, pt[1] + y_shift]
]
)
concat(left_curve, [for (i = [len(right_curve)-1 : -1 : 0]) right_curve[i]]);
module stator(diameter, height, p_height, ports) {
difference() {
// Create column
column(diameter, diameter - (stator_wall_thickness * 2), height);
// Subtract ports
translate([0, 0, (height - p_height) / 2])
ports(diameter, p_height, number_of_ports);
// 4 M3 mounting holes
for (i = [0:360/number_of_mounting_holes:360]) {
// Top
rotate([0, 0, i])
translate([stator_screw_offset, 0, -1])
cylinder(d = screw_insert_diameter, h = screw_length + 1);
// Bottom
rotate([0, 0, i])
translate([stator_screw_offset, 0, height - screw_length]) // Adjust position as needed
cylinder(d = screw_insert_diameter, h = screw_length + 1);
}
}
}
module impeller_blades_straight(rotor_inner_diameter, rotor_height) {
difference() {
intersection() {
// Column with full diameter
cylinder(d = rotor_inner_diameter, h = rotor_height);
// Blades
union() {
for (i = [0:360/number_of_ports:360]) {
rotate([0, 0, i - 90])
translate([0, rotor_inner_diameter / 2, 0])
rotate([0, 0, blade_angle])
translate([-rotor_wall_thickness, -(rotor_inner_diameter / 2), 0])
linear_extrude(height = rotor_height, twist = 0, slices=360)
square([rotor_wall_thickness, diameter]);
}
}
}
// Subtract core cylinder
translate([0, 0, -1])
cylinder(d = diameter * pressure_zone, h = height + 10);
}
}
module impeller_blade(radius, rotor_height){
// Generate points
points1 = arc_between_points([0, -hub_diameter / 2], [radius, 0], -blade_angle);
points2 = [for(p = [len(points1)-1:-1:0]) points1[p] - [0, -rotor_wall_thickness]];
// Create the blade by combining top and bottom points
linear_extrude(height = rotor_height)
polygon(concat(
points1,
points2
));
}
module impeller_blades_curved(radius, rotor_height){
difference() {
union() {
// Rotate and create blades
for (i = [0:360/number_of_ports:360]) {
rotate([0, 0, i])
impeller_blade(radius, rotor_height);
}
}
// Subtract core cylinder
/*
rounding_radius = 25;
translate([0, 0, rotor_wall_thickness + rounding_radius])
minkowski() {
// Original cylinder, reduced by twice the rounding radius
cylinder(
d = diameter * pressure_zone - (2 * rounding_radius),
h = 1000
);
// Sphere for rounding
sphere(r = rounding_radius);
}
*/
top_rounding_radius = 5;
bottom_rounding_radius = rotor_height / 2 - 1;
translate([0, 0, rotor_height - top_rounding_radius - 0])
union(){
difference() {
// Column with diameter = 2 * top_rounding_radius
cylinder(d = (diameter * pressure_zone) + (top_rounding_radius * 2), h = top_rounding_radius + 10);
// Top torus
rotate_extrude()
translate([((diameter * pressure_zone) + (top_rounding_radius * 2)) / 2, 0, 0])
circle(r = top_rounding_radius);
}
rotate([180, 0, 0])
minkowski() {
// Original cylinder, reduced by twice the rounding radius
cylinder(
d = (diameter * pressure_zone) - (2 * bottom_rounding_radius),
h = rotor_height - bottom_rounding_radius - (top_rounding_radius * 2) + 2
);
// Sphere for rounding
sphere(r = bottom_rounding_radius);
}
}
}
}
module rotor() {
rotor_diameter = diameter - (stator_wall_thickness * 2) - (tolerance * 2);
rotor_height = height - (tolerance * 2);
rotor_inner_diameter = rotor_diameter - (rotor_wall_thickness * 2);
difference() {
union(){
difference() {
// Outer cylinder
cylinder(d = rotor_diameter, h = rotor_height);
// Subtract inner cylinder
translate([0, 0, rotor_wall_thickness])
cylinder(d = rotor_inner_diameter, h = rotor_height + 2);
// Subtract ports
translate([0, 0, -tolerance])
ports();
}
/*
// Central column for hub added after subtraction
translate([0, 0, rotor_wall_thickness])
cylinder(d = hub_diameter, h = hub_height);
*/
// Add blades
impeller_blades_curved(rotor_inner_diameter / 2, rotor_height);
}
translate([0, 0, -10])
cylinder(d = hub_diameter, h = 100);
translate([0, 0, 0])
hub_attachment(0.2);
/*
// Subtract motor shaft hole
translate([0, 0, -10])
intersection(){
cylinder(d = motor_shaft_diameter, h = 100);
cube([motor_shaft_diameter, motor_shaft_diameter-1, 100], center = true);
}
*/
}
}
module hub() {
// Subtract motor shaft hole
difference(){
cylinder(d = hub_diameter, h = hub_height + 3);
translate([0, 0, -1])
intersection(){
cylinder(d = motor_shaft_diameter, h = 100);
cube([motor_shaft_diameter, motor_shaft_diameter-1, 100], center = true);
};
cylinder(d = motor_shaft_diameter, h = 2);
}
hub_attachment();
}
module hub_attachment(aug = 0) {
outer_diameter = hub_diameter + (tolerance * 6) + (aug * 2);
mid_diameter = hub_diameter + (tolerance * 2) + (aug * 2);
inner_diameter = outer_diameter - (tolerance * 2) - (aug * 2);
difference(){
column(outer_diameter, inner_diameter, rotor_wall_thickness);
translate([0, 0, -10])
ports();
}
translate([0, 0, rotor_wall_thickness])
column(outer_diameter, mid_diameter, rotor_wall_thickness);
column(mid_diameter, hub_diameter - (aug * 2), rotor_wall_thickness * 2);
}
module stator_top() {
difference() {
// Rounded top
intersection() {
// Unrounded full cylinder
cylinder(d = diameter, h = stator_wall_thickness);
// Add Minkowski rounded version
minkowski() {
cylinder(h = stator_wall_thickness - round_radius, d = diameter - round_radius * 2);
sphere(r = round_radius);
}
}
// Subtract central opening for pressure zone
translate([0, 0, -1])
cylinder(d = diameter * pressure_zone, h = stator_wall_thickness + 2);
// Subtract mounting holes for stator
for (i = [0:360/number_of_mounting_holes:360]) {
// Subtract screw hole
rotate([0, 0, i])
translate([stator_screw_offset, 0, -1]) // Adjusted position
cylinder(d = screw_diameter, h = stator_wall_thickness + 2);
// Subtract counterbore
rotate([0, 0, i])
translate([stator_screw_offset, 0, stator_wall_thickness - screw_bore_depth]) // Adjusted position
cylinder(d = screw_bore_diameter, h = stator_wall_thickness);
}
}
}
module stator_bottom() {
difference() {
// Rounded bottom
intersection() {
// Unrounded full cylinder
cylinder(d = diameter, h = stator_wall_thickness);
// Minkowski rounded version
minkowski() {
cylinder(h = stator_wall_thickness - round_radius, d = diameter - round_radius * 2);
sphere(r = round_radius);
}
}
// Subtract central hole for motor
translate([0, 0, -1])
cylinder(d = motor_diameter + (tolerance * 2), h = stator_wall_thickness + 5);
// Subtract mounting holes for stator
for (i = [0:360/number_of_mounting_holes:360]) {
// Subtract screw hole
rotate([0, 0, i])
translate([stator_screw_offset, 0, -1]) // Adjusted position
cylinder(d = screw_diameter, h = stator_wall_thickness + 2);
// Subtract counterbore
rotate([0, 0, i])
translate([stator_screw_offset, 0, stator_wall_thickness - screw_bore_depth]) // Adjusted position
cylinder(d = screw_bore_diameter, h = stator_wall_thickness);
}
// Subtract mounting holes for motor frame
rotate([180, 0, 0])
translate([0, 0, -stator_wall_thickness])
for (i = [0:360/number_of_mounting_holes:360]) {
// Subtract screw hole
rotate([0, 0, i])
translate([motor_frame_screw_offset, 0, -1]) // Adjusted position
cylinder(d = screw_diameter, h = stator_wall_thickness + 2);
// Subtract counterbore
rotate([0, 0, i])
translate([motor_frame_screw_offset, 0, stator_wall_thickness - screw_bore_depth]) // Adjusted position
cylinder(d = screw_bore_diameter, h = stator_wall_thickness);
}
}
}
module motor_frame() {
difference() {
intersection(){
union(){
// Rounded base
rotate([180, 0, 0])
translate([0, 0, -stator_wall_thickness])
intersection() {
// Unrounded full cylinder
cylinder(d = motor_frame_diameter, h = stator_wall_thickness);
// Minkowski rounded version
minkowski() {
cylinder(h = stator_wall_thickness - round_radius, d = motor_frame_diameter - round_radius * 2);
sphere(r = round_radius);
}
}
translate([0, 0, stator_wall_thickness])
// Outer cylinder
cylinder(d = motor_frame_diameter, h = motor_height);
}
// Outer cylinder
cylinder(d = motor_frame_diameter, h = motor_height);
}
// Subtract inner cylinder
translate([0, 0, stator_wall_thickness])
cylinder(d = motor_frame_diameter - (stator_wall_thickness * 2), h = motor_height + 2);
// Subtract ports
rotate([0, 0, -90/number_of_mounting_holes])
ports(number_of_mounting_holes);
intersection(){
difference() {
// Inner cylinder (subtract)
translate([0, 0, -1])
cylinder(d = (motor_frame_screw_offset * 2) - stator_wall_thickness, h = motor_height + 2);
translate([0, 0, 0])
cylinder(d = (motor_screw_y_offset * 2) + stator_wall_thickness, h = motor_height + 2);
}
// Subtract ports
translate([0, 0, -stator_wall_thickness - 2])
rotate([0, 0, -90/number_of_mounting_holes])
ports(number_of_mounting_holes);
}
// Subtract mounting holes for frame
for (i = [0:360/number_of_mounting_holes:360]) {
rotate([0, 0, i])
translate([motor_frame_screw_offset, 0, motor_height - screw_length - 5]) // Adjusted position
cylinder(d = screw_insert_diameter, h = 15);
}
// Subtract magnet hole
translate([0, 0, -10])
cylinder(d = magnet_diameter + (tolerance * 2), h = 100);
// Subtract mounting holes for motor
for (i = [0:360/number_of_mounting_holes:360]) {
rotate([0, 0, i]) {
// Use modulo to alternate between x and y offsets
motor_screw_offset = (floor(i / (360/number_of_mounting_holes)) % 2 == 0) ?
motor_screw_x_offset : motor_screw_y_offset;
translate([motor_screw_offset, 0, -1])
cylinder(d = screw_diameter, h = 15);
}
}
}
}
module base() {
union(){
difference(){
rotate_extrude() {
polygon(curved_polygon(
points = [
[diameter / 3, motor_height],
[motor_frame_diameter / 2, motor_height - 5],
[motor_frame_diameter / 2, 0],
[motor_frame_diameter / 2, -10],
[diameter / 3, -15]
],
steps = 100,
x_shift = -stator_wall_thickness,
y_shift = 0
));
}
// Subtract ports
translate([0, 0, -25])
rotate([0, 0, -90/number_of_mounting_holes])
ports(number_of_mounting_holes, 45);
/*
translate([0, 0, -19])
// Subtract mounting holes for stator
for (i = [0:360/number_of_mounting_holes:360]) {
// Subtract screw hole
rotate([0, 0, i])
translate([stator_screw_offset, 0, -1]) // Adjusted position
cylinder(d = screw_diameter, h = stator_wall_thickness + 2);
// Subtract counterbore
rotate([0, 0, i])
translate([stator_screw_offset, 0, stator_wall_thickness - screw_bore_depth]) // Adjusted position
cylinder(d = screw_bore_diameter, h = stator_wall_thickness);
}
*/
}
motor_frame();
translate([0, 0, -23])
stator_top();
}
}
stator(siren_diameter, siren_height, port_height, number_of_ports);
//translate([diameter - 10, 0, tolerance])
/*
intersection(){
cylinder(h = 30, d = 30);
rotor();
}
*/
rotor();
/*
translate([diameter - 10, 0, height + 10])
hub_attachment();
*/
/*
translate([0, 0, height + 2])
stator_top();
rotate([180, 0, 0])
translate([0, 0, 2])
stator_bottom();
//translate([0, 0, -motor_height - 10])
//motor_frame();
translate([0, 0, -motor_height - 15])
base();
*/
/*
stator();
translate([diameter + 10, 0, tolerance])
rotor();
translate([diameter * 2 + 10, 0, height + 2])
stator_top();
rotate([180, 0, 0])
translate([-diameter - 10, 0, 2])
stator_bottom();
translate([-diameter * 2 - 10, 0, -motor_height - 10])
motor_frame();
translate([-diameter * 3 - 10, 0, -motor_height - 10])
base();
*/
/*
// for testing
difference(){
cylinder(h=5.5, d=20);
translate([-5, 0, -1])
cylinder(h=10, d=screw_diameter);
translate([-5, 0, 3])
cylinder(h=5, d=screw_bore_diameter);
translate([5, 0, -1])
cylinder(h=10, d=screw_insert_diameter);
}
*/

View file

@ -0,0 +1,75 @@
// Air Raid Siren Stator Parameters
outer_diameter = 200; // Outer diameter of the stator
column_height = 100; // Height of the entire column
num_ports = 7; // Number of rectangular ports (works with odd or even)
port_height = 70; // Height of the ports
wall_thickness = 5; // Thickness of the column wall
module air_raid_siren_stator() {
// Calculate port width based on equal arc length
port_width = (PI * outer_diameter) / (num_ports * 2);
// Calculate vertical position to center the ports
port_vertical_offset = (column_height - port_height) / 2;
// Calculate inner diameter based on wall thickness
inner_diameter = outer_diameter - (2 * wall_thickness);
difference() {
union() {
// Main cylindrical body
cylinder(h = column_height, d = outer_diameter, $fn = 100);
// Close the bottom with wall_thickness height
cylinder(h = wall_thickness, d = outer_diameter, $fn = 100);
}
// Hollow out the center (open on top end)
translate([0, 0, wall_thickness])
cylinder(h = column_height + 1, d = inner_diameter, $fn = 100);
// Create evenly spaced rectangular ports
for (i = [0 : num_ports - 1]) {
rotate([0, 0, i * (360 / num_ports)])
translate([outer_diameter/2 - wall_thickness * 2, -port_width/2, port_vertical_offset])
cube([wall_thickness * 2 + 1, port_width, port_height]);
}
}
}
module impeller() {
// Calculate port width based on equal arc length
port_width = (PI * outer_diameter) / (num_ports * 2);
// Calculate vertical position to center the ports
port_vertical_offset = (column_height - port_height) / 2;
// Calculate inner diameter based on wall thickness
inner_diameter = outer_diameter - (2 * wall_thickness);
difference() {
union() {
// Main cylindrical body
cylinder(h = column_height, d = outer_diameter, $fn = 100);
// Close the bottom with wall_thickness height
cylinder(h = wall_thickness, d = outer_diameter, $fn = 100);
}
// Hollow out the center (open on top end)
translate([0, 0, wall_thickness])
cylinder(h = column_height + 1, d = inner_diameter, $fn = 100);
// Create evenly spaced rectangular ports
for (i = [0 : num_ports - 1]) {
rotate([0, 0, i * (360 / num_ports)])
translate([outer_diameter/2 - wall_thickness * 2, -port_width/2, port_vertical_offset])
cube([wall_thickness * 2 + 1, port_width, port_height]);
}
}
}
// Render the stator
//air_raid_siren_stator();
impeller();

View file

@ -0,0 +1,313 @@
/* [Stator Parameters] */
Outer_Diameter = 200; //[10:1:500] Outer diameter of the stator
Column_Height = 100; //[10:1:500] Height of the entire column
Base_Height = 5; //[1:0.1:50] Height of the base
Number_of_Ports = 7; //[3:1:20] Number of rectangular ports (works with odd or even)
Port_Height = 70; //[10:1:500] Height of the ports
Wall_Thickness = 5; //[1:0.1:50] Thickness of the column wall
Tolerance = 2; //[0:0.1:5]
/* [Hub Parameters] */
Hub_Diameter = 10; //[10:1:500] Diameter of the rotor hub
Shaft_Diameter = 5; //[1:0.1:50] Diameter of the motor shaft
/* [Blade Parameters] */
Blade_Thickness = 5; //[1:0.1:20] Thickness of each blade
module stator_core(outer_diameter, number_of_ports, column_height, port_height, base_height, wall_thickness, port_vertical_offset = 0) {
// Calculate port width based on equal arc length
port_width = (PI * outer_diameter) / (number_of_ports * 2);
// Calculate default vertical position to center the ports if no offset is provided
default_port_vertical_offset = (column_height - port_height) / 2;
// Use the provided offset or fall back to the centered position
final_port_vertical_offset = port_vertical_offset != 0 ?
default_port_vertical_offset - (port_vertical_offset / 2):
default_port_vertical_offset;
// Calculate inner diameter based on wall thickness
inner_diameter = outer_diameter - wall_thickness;
difference() {
union() {
// Base cylinder
cylinder(h = base_height, d = outer_diameter, $fn = 100);
// Main cylindrical body
translate([0, 0, base_height])
cylinder(h = column_height, d = outer_diameter, $fn = 100);
}
// Hollow out the center (open on top end)
translate([0, 0, base_height])
cylinder(h = column_height + 1, d = inner_diameter, $fn = 100);
// Create evenly spaced rectangular ports
for (i = [0 : number_of_ports - 1]) {
rotate([0, 0, i * (360 / number_of_ports)])
translate([outer_diameter/2 - wall_thickness * 2, -port_width/2, base_height + final_port_vertical_offset])
cube([wall_thickness * 2 + 1, port_width, port_height], center = false);
}
}
}
module stator() {
// Local variables set to global variables
outer_diameter = Outer_Diameter;
number_of_ports = Number_of_Ports;
column_height = Column_Height;
port_height = Port_Height;
base_height = Base_Height;
wall_thickness = Wall_Thickness;
// Use the module instead of calling it as a function
stator_core(outer_diameter, number_of_ports, column_height, port_height, base_height, wall_thickness);
}
module stator() {
// Local variables set to global variables
outer_diameter = Outer_Diameter;
number_of_ports = Number_of_Ports;
column_height = Column_Height;
port_height = Port_Height;
base_height = Base_Height;
wall_thickness = Wall_Thickness;
// Extension dimensions
extension_length = 20; // Length of extension
extension_width = 15; // Width of extension
fillet_radius = 2; // Fillet radius
screw_hole_diameter = 4; // M4 screw hole
support_ring_diameter = screw_hole_diameter + 6;
difference() {
union() {
// Original stator core
stator_core(outer_diameter, number_of_ports, column_height, port_height, base_height, wall_thickness);
// Top extensions at 4 cardinal points
translate([0, 0, base_height + column_height - wall_thickness])
for (i = [0 : 3]) {
rotate([0, 0, i * 90])
translate([outer_diameter/2, -extension_width/2, 0])
hull() {
// Corners with fillet
// Bottom left
translate([fillet_radius, fillet_radius, 0])
cylinder(r = fillet_radius, h = wall_thickness, $fn = 50);
// Bottom right
translate([extension_length - fillet_radius, fillet_radius, 0])
cylinder(r = fillet_radius, h = wall_thickness, $fn = 50);
// Top left
translate([fillet_radius, extension_width - fillet_radius, 0])
cylinder(r = fillet_radius, h = wall_thickness, $fn = 50);
// Top right
translate([extension_length - fillet_radius, extension_width - fillet_radius, 0])
cylinder(r = fillet_radius, h = wall_thickness, $fn = 50);
}
}
}
// M4 Screw holes through top extensions
translate([0, 0, base_height + column_height - wall_thickness])
for (i = [0 : 3]) {
rotate([0, 0, i * 90])
translate([outer_diameter/2 + extension_length/2, 0, -1])
cylinder(h = wall_thickness + 2, d = screw_hole_diameter, $fn = 50);
}
}
}
/*
module blade(outer_diameter, column_height, blade_thickness, blade_color) {
// Bezier curve function (cubic)
function bezier_cubic(p0, p1, p2, p3, t) =
pow(1-t, 3) * p0 +
3 * pow(1-t, 2) * t * p1 +
3 * (1-t) * pow(t, 2) * p2 +
pow(t, 3) * p3;
// Generate Bezier curve points
function generate_bezier_points(p0, p1, p2, p3, steps=20) =
[for (i = [0:steps]) bezier_cubic(p0, p1, p2, p3, i/steps)];
// Control points for the Bezier curve
p0 = [0, 0]; // Start point
p1 = [outer_diameter / 3, column_height];
p2 = [outer_diameter / 8, column_height];
p3 = [outer_diameter / 2, column_height]; // End point
// Generate curve points
curve_points = generate_bezier_points(p0, p1, p2, p3);
// Combine points to create polygon
points = concat(
curve_points,
[[outer_diameter/2, 0]] // Close the polygon
);
color(blade_color)
rotate([90, 0, 0]) // Rotate 90 degrees around X-axis
linear_extrude(height = blade_thickness, center = true)
polygon(points = points);
}
*/
module blade(outer_diameter, column_height, blade_thickness, blade_color) {
// Rectangular blade points
points = [
[outer_diameter * 0.5 / 2, 0], // Start at 40% of radius
[outer_diameter * 0.5 / 2, column_height],// Top left
[outer_diameter / 2, column_height],// Top right
[outer_diameter / 2, 0] // Bottom right
];
color(blade_color)
rotate([90, 0, 0]) // Rotate 90 degrees around X-axis
linear_extrude(height = blade_thickness, center = true)
polygon(points = points);
}
module rotor() {
// Local variables set to global variables
outer_diameter = Outer_Diameter - Wall_Thickness - Tolerance;
number_of_ports = Number_of_Ports;
column_height = Column_Height - Base_Height - (Tolerance * 2);
port_height = Port_Height;
base_height = Base_Height;
wall_thickness = Wall_Thickness;
port_vertical_offset = Tolerance * 2 + 1;
// Hub parameters from global variables
hub_diameter = Hub_Diameter;
shaft_diameter = Shaft_Diameter;
hub_height = base_height * 2;
// Blade parameters from global variables
number_of_blades = Number_of_Ports;
blade_thickness = Blade_Thickness;
rotation_offset = (360 / (number_of_ports * 4)) +
((blade_thickness / (PI * outer_diameter)) * 360 / 2);
difference() {
union() {
// Original stator_core module call
stator_core(outer_diameter, number_of_ports, column_height, port_height, base_height, wall_thickness, port_vertical_offset);
// Hub for motor attachment
translate([0, 0, 0])
cylinder(h = hub_height, d = hub_diameter, $fn = 100);
// Add blades
for (i = [0 : number_of_blades - 1]) {
rotate([0, 0, i * (360 / number_of_ports) + rotation_offset])
//rotate([0, 0, i * (360 / number_of_ports) + rotation_offset - 10])
mirror([0, 1, 0]) // Mirror along Z-axis
translate([0, 0, base_height])
//translate([40, 0, base_height])
//rotate([0, 0, -20])
//translate([-40, 0, 0])
blade(
outer_diameter - wall_thickness,
column_height,
blade_thickness,
"red" // Blade color
);
}
}
// Shaft hole for motor attachment
translate([0, 0, -1])
cylinder(h = hub_height + 2, d = shaft_diameter, $fn = 100);
}
}
module stator_cap() {
outer_diameter = Outer_Diameter;
wall_thickness = Wall_Thickness;
column_height = Column_Height;
base_height = Base_Height;
// Central hole diameter (60% of total diameter)
central_hole_diameter = outer_diameter * 0.6;
// M4 screw specifications
screw_hole_diameter = 4;
// Extension dimensions
extension_length = 20; // Length of extension
extension_width = 15; // Width of extension
fillet_radius = 2; // Fillet radius
// Screw hole support ring
support_ring_diameter = screw_hole_diameter + 6; // Adds extra material around screw hole
difference() {
union() {
// Base solid cylinder
cylinder(h = wall_thickness, d = outer_diameter, $fn = 100);
// Screw hole extensions at 4 cardinal points
for (i = [0 : 3]) {
rotate([0, 0, i * 90])
translate([outer_diameter/2 - wall_thickness, -extension_width/2, 0]) {
difference() {
// Filleted extension
hull() {
// Corners with fillet
// Bottom left
translate([fillet_radius, fillet_radius, 0])
cylinder(r = fillet_radius, h = wall_thickness, $fn = 50);
// Bottom right
translate([extension_length - fillet_radius, fillet_radius, 0])
cylinder(r = fillet_radius, h = wall_thickness, $fn = 50);
// Top left
translate([fillet_radius, extension_width - fillet_radius, 0])
cylinder(r = fillet_radius, h = wall_thickness, $fn = 50);
// Top right
translate([extension_length - fillet_radius, extension_width - fillet_radius, 0])
cylinder(r = fillet_radius, h = wall_thickness, $fn = 50);
}
// Centered screw hole support ring
translate([extension_length/2, extension_width/2, -1])
cylinder(h = wall_thickness + 2, d = support_ring_diameter, $fn = 100);
}
}
}
}
// Central hole
translate([0, 0, -1])
cylinder(h = wall_thickness + 2, d = central_hole_diameter, $fn = 100);
// M4 Screw holes through extensions
for (i = [0 : 3]) {
rotate([0, 0, i * 90])
translate([outer_diameter/2 + extension_length/2 - wall_thickness, 0, -1])
cylinder(h = wall_thickness + 2, d = screw_hole_diameter, $fn = 50);
}
}
}
// Render components separately
stator();
/*
translate([Outer_Diameter, 0, Base_Height + Tolerance])
rotor();
// Render cap separately and offset
translate([0, 0, Base_Height + Column_Height + Tolerance])
stator_cap();
*/

BIN
designs/stl/back_face.stl Normal file

Binary file not shown.

BIN
designs/stl/base.stl Normal file

Binary file not shown.

BIN
designs/stl/base_125.stl Normal file

Binary file not shown.

BIN
designs/stl/bottom_face.stl Normal file

Binary file not shown.

Binary file not shown.

BIN
designs/stl/box_snap.stl Normal file

Binary file not shown.

Binary file not shown.

BIN
designs/stl/box_test.stl Normal file

Binary file not shown.

BIN
designs/stl/dowel.stl Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
designs/stl/front_face.stl Normal file

Binary file not shown.

BIN
designs/stl/hub.stl Normal file

Binary file not shown.

BIN
designs/stl/hub_3_port.stl Normal file

Binary file not shown.

BIN
designs/stl/hub_4_port.stl Normal file

Binary file not shown.

BIN
designs/stl/hub_5_port.stl Normal file

Binary file not shown.

Binary file not shown.

BIN
designs/stl/hub_6_port.stl Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
designs/stl/hub_test.stl Normal file

Binary file not shown.

Binary file not shown.

BIN
designs/stl/motor_frame.stl Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
designs/stl/rotor.stl Normal file

Binary file not shown.

BIN
designs/stl/rotor_125.stl Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
designs/stl/rotor_test.stl Normal file

Binary file not shown.

BIN
designs/stl/side_face.stl Normal file

Binary file not shown.

Binary file not shown.

BIN
designs/stl/snap_box.stl Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
designs/stl/stator.stl Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
designs/stl/stator_top.stl Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
designs/stl/top_face.stl Normal file

Binary file not shown.

View file

@ -9,7 +9,9 @@
// Motor instance // Motor instance
//BLDCMotor motor = BLDCMotor(7, 0.14, 2450, 0.00003); //BLDCMotor motor = BLDCMotor(7, 0.14, 2450, 0.00003);
//BLDCMotor motor = BLDCMotor(7, 0.15, 940, 0.000125); //BLDCMotor motor = BLDCMotor(7, 0.15, 940, 0.000125);
BLDCMotor motor = BLDCMotor(7, 0.14, 850, 0.0000425); //BLDCMotor motor = BLDCMotor(7, 0.14, 850, 0.0000425);
//BLDCMotor motor = BLDCMotor(7, 0.14, 950, 0.000012);
BLDCMotor motor = BLDCMotor(7, 0.14, 950, 0.000012);
BLDCDriver6PWM driver = BLDCDriver6PWM(A_PHASE_UH, A_PHASE_UL, A_PHASE_VH, A_PHASE_VL, A_PHASE_WH, A_PHASE_WL); BLDCDriver6PWM driver = BLDCDriver6PWM(A_PHASE_UH, A_PHASE_UL, A_PHASE_VH, A_PHASE_VL, A_PHASE_WH, A_PHASE_WL);
// Gain calculation shown at https://community.simplefoc.com/t/b-g431b-esc1-current-control/521/21 // Gain calculation shown at https://community.simplefoc.com/t/b-g431b-esc1-current-control/521/21
LowsideCurrentSense currentSense = LowsideCurrentSense(0.003f, -64.0f/7.0f, A_OP1_OUT, A_OP2_OUT, A_OP3_OUT); LowsideCurrentSense currentSense = LowsideCurrentSense(0.003f, -64.0f/7.0f, A_OP1_OUT, A_OP2_OUT, A_OP3_OUT);
@ -83,9 +85,13 @@ void setup() {
motor.PID_velocity.P = 0.01; motor.PID_velocity.P = 0.01;
motor.PID_velocity.I = 0.004; motor.PID_velocity.I = 0.004;
driver.voltage_limit = 10; driver.voltage_limit = 12;
motor.voltage_limit = 5; motor.voltage_limit = 6;
//motor.current_limit = 0.2; motor.current_limit = 4.5;
//driver.voltage_limit = 2;
//motor.voltage_limit = 1;
//motor.current_limit = 2;
motor.PID_velocity.limit = motor.voltage_limit; motor.PID_velocity.limit = motor.voltage_limit;
@ -97,9 +103,10 @@ void setup() {
motor.LPF_velocity.Tf = 0.01; motor.LPF_velocity.Tf = 0.01;
// comment out if not needed // comment out if not needed
//motor.useMonitoring(Serial); motor.useMonitoring(Serial);
motor.monitor_variables = _MON_CURR_Q;
//motor.monitor_downsample = 100000; motor.monitor_downsample = 10000;
motor.motion_downsample = 1; motor.motion_downsample = 1;
// initialize motor // initialize motor
@ -130,10 +137,9 @@ void loop() {
// Motion control function // Motion control function
motor.move(targetV); motor.move(targetV);
if (abs(motor.target) < 5.0f && motor.enabled==1)
if (abs(motor.target) < 20.0f && motor.enabled==1)
motor.disable(); motor.disable();
if (abs(motor.target) >= 20.0f && motor.enabled==0) if (abs(motor.target) >= 5.0f && motor.enabled==0)
motor.enable(); motor.enable();
// function intended to be used with serial plotter to monitor motor variables // function intended to be used with serial plotter to monitor motor variables
@ -152,4 +158,6 @@ void loop() {
*/ */
commandESP.run(); commandESP.run();
//commandComp.run(); //commandComp.run();
motor.monitor();
} }

View file

@ -0,0 +1,377 @@
#include "WiFi.h"
#include <Wire.h>
// Display Libraries
#include <Wire.h>
#include <SSD1306Ascii.h>
#include <SSD1306AsciiWire.h>
// Other Libraries
#include <arduino-timer.h>
#include <AiEsp32RotaryEncoder.h>
#include <ArduinoOSCWiFi.h>
#include <Preferences.h>
// WiFi stuff
const char* ssid = "sirening";
const char* pwd = "alarm_11735";
int wifiStatus = 1;
// for ArduinoOSC
const char* host = "192.168.4.200";
const int settings_port = 54000;
const int state_port = 54001;
// Button Defs
bool buttonState1 = false;
AiEsp32RotaryEncoder encoderButton1 = AiEsp32RotaryEncoder(34, 35, 32, -1, 4);
//bool ampButtonState = false;
//AiEsp32RotaryEncoder ampEncoderButton = AiEsp32RotaryEncoder(33, 25, 26, -1, 4);
int menuSelect = 0; //0 = freq, 1 = ports, 2 = ip, 3 = wifi
float frequency = 0;
//float amplitude = 0;
int ports = 6;
int motorIndex = 2;
// Display Defs
#define I2C_ADDRESS 0x3C
SSD1306AsciiWire oled;
// Preferences
#define RW_MODE false
#define RO_MODE true
Preferences prefs;
HardwareSerial freqSerial(1);
void killAll() {
updateFreq(0.00);
OscWiFi.send(host, state_port, "/state", motorIndex, 0);
}
float hzToRads(float freq) {
return freq * TWO_PI / ports;
}
float rpmToRads(float rpm) {
return (rpm / 60) * TWO_PI;
}
void updateFreq(float val) {
frequency = val;
encoderButton1.setEncoderValue(frequency * 10.0);
freqSerial.print("\nV" + String(hzToRads(frequency)) + "\n");
updateDisplayValue(String(frequency));
Serial.println("Frequency: " + String(frequency));
}
/*
void updateAmp(float val){
amplitude = val;
ampEncoderButton.setEncoderValue(amplitude * 10.0);
ampSerial.print("\nV" + String(hzToRads(amplitude)) + "\n");
updateDisplayValue(amplitude);
Serial.println("Amplitude: " + String(amplitude));
}
*/
void setupPrefs() {
prefs.begin("prefs", RO_MODE);
bool tpInit = prefs.isKey("nvsInit");
if (tpInit == false) {
prefs.end();
prefs.begin("prefs", RW_MODE);
prefs.putUInt("ports", ports);
prefs.putUInt("motorIndex", motorIndex);
prefs.putUInt("wifiStatus", wifiStatus);
prefs.putBool("nvsInit", true);
prefs.end();
prefs.begin("prefs", RO_MODE);
}
ports = prefs.getUInt("ports");
motorIndex = prefs.getUInt("motorIndex");
wifiStatus = prefs.getUInt("wifiStatus");
prefs.end();
}
void setupOSC() {
//OscWiFi.publish(host, state_port, "/state", motorIndex, frequency, amplitude)->setFrameRate(10.f);
//OscWiFi.publish(host, state_port, "/state", motorIndex, frequency)->setFrameRate(10.f);
OscWiFi.subscribe(settings_port, "/kill", [](const OscMessage& m) {
killAll();
});
OscWiFi.subscribe(settings_port, "/freq", [](const OscMessage& m) {
float input = m.arg<float>(0);
if (input < 500.0 && input >= 0.0) {
updateFreq(input);
}
});
}
void IRAM_ATTR readFreqEncoderISR() {
encoderButton1.readEncoder_ISR();
}
void setupEncoderButton(AiEsp32RotaryEncoder& eb, char* val) {
eb.begin();
if (val == "freq") {
eb.setup(readFreqEncoderISR);
eb.setBoundaries(0, 5000, false);
eb.setAcceleration(1000);
}
}
//This needs to be more sophisticated. Moving on if it disconnects...
void initWiFi() {
IPAddress ip(192, 168, 4, 200 + motorIndex);
IPAddress gateway(192, 168, 4, 1);
IPAddress subnet(255, 255, 0, 0);
int attempts = 0;
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, pwd);
WiFi.config(ip, gateway, subnet);
updateDisplayWiFi("Connecting to WiFi", "");
//Serial.print("Connecting to WiFi");
while (WiFi.status() != WL_CONNECTED && attempts < 5) {
//Serial.print('.');
attempts += 1;
delay(1000);
}
if (WiFi.status() != WL_CONNECTED) {
updateDisplayWiFi("Could not connect", "");
//Serial.print("Could not Connect");
delay(1000);
//WiFi.disconnect(true);
//WiFi.mode(WIFI_OFF);
}
Serial.println(WiFi.localIP());
}
void disconnectWiFi() {
WiFi.disconnect(true); // Disconnect and clear credentials
//WiFi.mode(WIFI_OFF); // Turn off WiFi radio
delay(100); // Small delay to ensure disconnection
updateDisplayWiFi("DISCONNECTED", "connect");
}
void setup() {
Serial.begin(115200);
freqSerial.begin(115200, SERIAL_8N1, 18, 19);
delay(2000);
setupPrefs();
setupEncoderButton(encoderButton1, "freq");
Wire.begin();
oled.begin(&Adafruit128x64, I2C_ADDRESS);
//oled.setI2cClock(100000L);
oled.setFont(System5x7);
oled.clear();
if (wifiStatus == 1) {
initWiFi();
setupOSC();
}
updateDisplayMenu("Frequency");
oled.setCursor(4, 3);
oled.println("------------------");
updateDisplayValue("0.00");
}
void encoderButtonUpdate(AiEsp32RotaryEncoder& eb, bool& buttonState) {
if (eb.encoderChanged()) {
oled.setCursor(12, 5);
if (menuSelect == 0) {
frequency = eb.readEncoder() / 10.0;
freqSerial.print("\nV" + String(hzToRads(frequency)) + "\n");
OscWiFi.send(host, state_port, "/state", motorIndex, frequency);
updateDisplayValue(String(frequency));
Serial.println("Frequency: " + String(frequency));
}
if (menuSelect == 1) {
ports = eb.readEncoder();
prefs.begin("prefs", RW_MODE);
prefs.putUInt("ports", ports);
prefs.end();
updateDisplayValue(String(ports));
Serial.println("No. of Ports: " + String(ports));
}
if (menuSelect == 2) {
motorIndex = eb.readEncoder();
prefs.begin("prefs", RW_MODE);
prefs.putUInt("motorIndex", motorIndex);
prefs.end();
updateDisplayValue(String(motorIndex));
Serial.println("Siren Index: " + String(motorIndex));
}
if (menuSelect == 3) {
wifiStatus = eb.readEncoder();
prefs.begin("prefs", RW_MODE);
if (wifiStatus == 1) {
initWiFi();
setupOSC();
prefs.putUInt("wifiStatus", wifiStatus);
if (WiFi.status() != WL_CONNECTED) {
eb.setEncoderValue(0);
}
if (WiFi.status() != WL_CONNECTED) {
eb.setEncoderValue(0);
prefs.putUInt("wifiStatus", 0);
updateDisplayWiFi("DISCONNECTED", "connect");
//WiFi.disconnect(true);
//WiFi.mode(WIFI_OFF);
} else {
updateDisplayWiFi("disconnect", "CONNECTED");
}
} else {
prefs.putUInt("wifiStatus", wifiStatus);
disconnectWiFi();
eb.setEncoderValue(0);
}
prefs.end();
}
}
if (eb.isEncoderButtonDown() && !buttonState) {
buttonState = true;
}
//button is up
if (!eb.isEncoderButtonDown() && buttonState) {
prefs.begin("prefs", RW_MODE);
Serial.println(prefs.getUInt("motorIndex", motorIndex));
prefs.end();
if (frequency > 0) {
killAll();
} else {
menuSelect = (menuSelect + 1) % 4;
if (menuSelect == 0) {
updateDisplayMenu("Frequency");
eb.setBoundaries(0, 5000, false);
eb.setAcceleration(1000);
eb.setEncoderValue(frequency * 10.0);
updateDisplayValue(String(frequency));
Serial.println("Frequency: " + String(frequency));
}
if (menuSelect == 1) {
updateDisplayMenu("No. of Ports");
eb.setBoundaries(2, 12, false);
eb.setAcceleration(1);
eb.setEncoderValue(ports);
updateDisplayValue(String(ports));
Serial.println("No. of Ports: " + String(ports));
}
if (menuSelect == 2) {
updateDisplayMenu("Siren Index");
eb.setBoundaries(1, 8, false);
eb.setAcceleration(1);
eb.setEncoderValue(motorIndex);
updateDisplayValue(String(motorIndex));
Serial.println("Motor Index: " + String(motorIndex));
}
if (menuSelect == 3) {
updateDisplayMenu("WiFi");
eb.setBoundaries(0, 1, false);
eb.setAcceleration(1);
if (WiFi.status() != WL_CONNECTED) {
eb.setEncoderValue(0);
updateDisplayWiFi("DISCONNECTED", "connect");
} else {
eb.setEncoderValue(1);
updateDisplayWiFi("disconnect", "CONNECTED");
}
}
}
buttonState = false;
}
}
void loop() {
encoderButtonUpdate(encoderButton1, buttonState1);
//encoderButtonUpdate("amp", amplitude, ampEncoderButton, ampButtonState, ampSerial);
OscWiFi.update();
//getSerialData();
//getFreqSerialData();
//getAmpSerialData();
}
void updateDisplayMenu(String string) {
oled.set1X();
oled.setCursor(4, 2);
oled.print(string);
oled.clearToEOL();
oled.println();
}
void updateDisplayValue(String string) {
oled.set2X();
oled.setCursor(12, 5);
oled.print(string);
oled.clearToEOL();
oled.println();
oled.clearToEOL();
oled.println();
}
void updateDisplayWiFi(String string1, String string2) {
oled.set1X();
oled.setCursor(12, 5);
oled.print(string1);
oled.clearToEOL();
oled.println();
oled.clearToEOL();
oled.println();
oled.setCursor(12, 7);
oled.print(string2);
oled.clearToEOL();
oled.println();
}
bool showSerialData(void*) {
Serial.print("\nrpm/freq real: ");
Serial.print(frequency);
Serial.print("\t");
Serial.print(frequency / 60 * ports);
return true;
}
void getFreqSerialData() {
while (freqSerial.available()) {
Serial.print(char(freqSerial.read()));
}
}
void getSerialData() {
if (Serial.available() > 0) {
// ignore line feed and carriage return
if (Serial.peek() == '\n' || Serial.peek() == '\r') {
Serial.read(); // read and discard LF & CR
} else {
frequency = Serial.parseFloat();
Serial.println(frequency, 4); // print to 4 decimal places
}
}
}

View file

@ -0,0 +1,230 @@
#include "WiFi.h"
#include <Wire.h>
// Display Libraries
#include <Wire.h>
#include <SSD1306Ascii.h>
#include <SSD1306AsciiWire.h>
// Other Libraries
#include <arduino-timer.h>
#include <AiEsp32RotaryEncoder.h>
#include <ArduinoOSCWiFi.h>
const int sirenCount = 4;
// WiFi stuff
const char* ssid = "sirening";
const char* pwd = "alarm_11735";
const IPAddress ip(192, 168, 4, 200);
const IPAddress gateway(192, 168, 4, 1);
const IPAddress subnet(255, 255, 0, 0);
// for ArduinoOSCo
String hosts[sirenCount];
const int settings_port = 54000;
const int state_port = 54001;
// Button Defs
bool buttonState1 = false;
unsigned long startTimeButtonDown = 0;
unsigned int button1Count = 0;
AiEsp32RotaryEncoder encoderButton1 = AiEsp32RotaryEncoder(34, 35, 32, -1, 4);
//bool ampButtonState = false;
//AiEsp32RotaryEncoder ampEncoderButton = AiEsp32RotaryEncoder(33, 25, 26, -1, 4);
int sirenSelect = 0;
//TODO: These should be bound to a publication of the frequency directly from the siren
float frequencies[sirenCount] = {0};
//float frequencyTargets[] = {0, 0, 0};
//float amplitudes[] = {0, 0, 0};
// Display Defs
#define I2C_ADDRESS 0x3C
SSD1306AsciiWire oled;
void killAll() {
for (int h = 0; h < sirenCount; h++) {
OscWiFi.send(hosts[h], settings_port, "/kill");
frequencies[h] = 0;
}
}
void setupOSC(char* path) {
OscWiFi.subscribe(state_port, path, [](const OscMessage& m) {
int motorIndex = m.arg<int>(0) - 1;
float frequency = m.arg<float>(1);
if (frequency != frequencies[motorIndex]) {
frequencies[motorIndex] = frequency;
encoderButton1.setEncoderValue(frequency * 10);
if (sirenSelect == motorIndex){
updateDisplayValue(String(frequency));
}
Serial.println("Frequency: " + String(frequency));
}
/*
if(frequency != frequencyTargets[motorIndex]){
OscWiFi.send(hosts[sirenSelect], settings_port, "/freq", frequencyTargets[sirenSelect]);
}
*/
});
OscWiFi.subscribe(state_port, "/freq", [](const OscMessage& m) {
int siren = m.arg<int>(0) - 1;
float input = m.arg<float>(1);
if (input < 500.0 && input >= 0.0) {
frequencies[siren] = input;
OscWiFi.send(hosts[siren], settings_port, "/freq", frequencies[siren]);
Serial.println("Frequency: " + String(frequencies[siren]));
encoderButton1.setEncoderValue(frequencies[sirenSelect] * 10.0);
updateDisplayValue(String(frequencies[sirenSelect]));
}
});
}
void IRAM_ATTR readFreqEncoderISR() {
encoderButton1.readEncoder_ISR();
}
/*
void IRAM_ATTR readAmpEncoderISR() {
ampEncoderButton.readEncoder_ISR();
}
*/
void setupEncoderButton(AiEsp32RotaryEncoder& eb, char* val) {
eb.begin();
if (val == "freq") {
eb.setup(readFreqEncoderISR);
eb.setBoundaries(0, 5000, false);
eb.setAcceleration(1000);
}
}
void setup() {
delay(2000);
// initialize serial communication at 115200 bits per second:
Serial.begin(115200); //used for debugging.
delay(2000);
for (int i = 0; i < sirenCount; i++) {
hosts[i] = "192.168.4." + String(200 + i + 1);
Serial.println(hosts[i]);
}
WiFi.softAPConfig(ip, gateway, subnet);
WiFi.softAP(ssid, pwd, 1, false, 5);
/*
Serial.println("");
Serial.print("WiFi AP IP = ");
Serial.println(WiFi.softAPIP());
*/
setupOSC("/state");
setupEncoderButton(encoderButton1, "freq");
//setupEncoderButton(ampEncoderButton, "amp");
Wire.begin();
oled.begin(&Adafruit128x64, I2C_ADDRESS);
//oled.setI2cClock(100000L);
oled.setFont(System5x7);
oled.clear();
updateDisplayMenu("Frequency (Siren 1)");
oled.setCursor(4, 3);
oled.println("------------------");
updateDisplayValue("0.00");
}
void encoderButtonUpdate(AiEsp32RotaryEncoder& eb, bool& buttonState, char* val) {
if((millis() - startTimeButtonDown) > 500){
button1Count = 0;
}
if (eb.encoderChanged()) {
frequencies[sirenSelect] = eb.readEncoder() / 10.0;
OscWiFi.send(hosts[sirenSelect], settings_port, "/" + String(val), frequencies[sirenSelect]);
updateDisplayValue(String(frequencies[sirenSelect]));
Serial.println("Frequency: " + String(frequencies[sirenSelect]));
}
if (eb.isEncoderButtonDown() && !buttonState) {
buttonState = true;
if(button1Count == 0) {
startTimeButtonDown = millis();
}
button1Count = button1Count + 1;
if(button1Count == 2 && (millis() - startTimeButtonDown <= 500)){
killAll();
sirenSelect = -1;//(sirenSelect - 2) % 3;
}
}
//button is up
if (!eb.isEncoderButtonDown() && buttonState) {
if(val == "freq"){
sirenSelect = (sirenSelect + 1) % sirenCount;
updateDisplayMenu("Frequency (Siren " + String((sirenSelect+1)) + ")");
encoderButton1.setEncoderValue(frequencies[sirenSelect] * 10.0);
//ampEncoderButton.setEncoderValue(amplitudes[sirenSelect] * 10.0);
}
updateDisplayValue(String(frequencies[sirenSelect]));
Serial.println("Frequency: " + String(frequencies[sirenSelect]));
buttonState = false;
}
}
void loop() {
encoderButtonUpdate(encoderButton1, buttonState1, "freq");
//encoderButtonUpdate(ampEncoderButton, ampButtonState, "amp");
OscWiFi.update();
}
void updateDisplayMenu(String string) {
oled.set1X();
oled.setCursor(4, 2);
oled.print(string);
oled.clearToEOL();
oled.println();
}
void updateDisplayValue(String string) {
oled.set2X();
oled.setCursor(12, 5);
oled.print(string);
oled.clearToEOL();
oled.println();
oled.clearToEOL();
oled.println();
}
bool showSerialData(void*) {
Serial.print("\nrpm/freq real: ");
Serial.print(frequencies[sirenSelect]);
//Serial.print("\t");
//Serial.print(frequencies[sirenSelect] / 60 * ports);
return true;
}
/*
void getSerialData() {
if (Serial.available() > 0) {
// ignore line feed and carriage return
if (Serial.peek() == '\n' || Serial.peek() == '\r') {
Serial.read(); // read and discard LF & CR
} else {
frequency[sirenSelect] = Serial.parseFloat();
Serial.println(frequency[sirenSelect], 4); // print to 4 decimal places
}
}
}
*/

0
v1/README.md Normal file
View file

Some files were not shown because too many files have changed in this diff Show more