🏮 Adds servo config table
This commit is contained in:
@@ -1,7 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Servos from './servos.svelte';
|
import Servos from './servos.svelte';
|
||||||
|
import ServoTable from './ServoTable.svelte';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="mx-0 my-1 flex flex-col space-y-4 sm:mx-8 sm:my-8">
|
<div class="mx-0 my-1 flex flex-col space-y-4 sm:mx-8 sm:my-8">
|
||||||
<Servos />
|
<Servos />
|
||||||
|
<ServoTable />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { api } from '$lib/api';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
export let data = {
|
||||||
|
servos: []
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateValue = (event, index, key) => {
|
||||||
|
data.servos[index][key] = event.target.innerText;
|
||||||
|
};
|
||||||
|
|
||||||
|
const syncConfig = async () => {
|
||||||
|
await api.post('/api/servo/config', data);
|
||||||
|
};
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
const result = await api.get('/api/servo/config');
|
||||||
|
if (result.isOk()) {
|
||||||
|
data = result.inner;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="table table-xs">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Center PWM</th>
|
||||||
|
<th>Center Angle</th>
|
||||||
|
<th>Direction</th>
|
||||||
|
<th>Conversion</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{#each data.servos as servo, index}
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
contenteditable="true"
|
||||||
|
on:blur={syncConfig}
|
||||||
|
on:input={event => updateValue(event, index, 'center_pwm')}
|
||||||
|
>
|
||||||
|
{servo.center_pwm}
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
contenteditable="true"
|
||||||
|
on:blur={syncConfig}
|
||||||
|
on:input={event => updateValue(event, index, 'center_angle')}
|
||||||
|
>
|
||||||
|
{servo.center_angle}
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
contenteditable="true"
|
||||||
|
on:blur={syncConfig}
|
||||||
|
on:input={event => updateValue(event, index, 'direction')}
|
||||||
|
>
|
||||||
|
{servo.direction}
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
contenteditable="true"
|
||||||
|
on:blur={syncConfig}
|
||||||
|
on:input={event => updateValue(event, index, 'conversion')}
|
||||||
|
>
|
||||||
|
{servo.conversion}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type { Servo } from "$lib/types/models";
|
|
||||||
import { createEventDispatcher } from "svelte";
|
|
||||||
export let servo: Servo;
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
|
||||||
|
|
||||||
const sweep = () => {
|
|
||||||
dispatch('sweep', {channel: servo.channel});
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h2 class="text-lg">{ servo.name }</h2>
|
|
||||||
<div class="flex gap-2 items-center">
|
|
||||||
Is inverted <input type="checkbox" bind:checked={servo.inverted} class="toggle"/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
Middle position <input type="number" bind:value={servo.center_angle} class="input input-bordered input-sm max-w-xs"/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="relative mb-6">
|
|
||||||
<label for="labels-range-input" class="sr-only">Labels range</label>
|
|
||||||
<input id="labels-range-input" type="range" bind:value={servo.angle} min="0" max="180" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700">
|
|
||||||
<span class="text-sm text-gray-500 dark:text-gray-400 absolute start-0 -bottom-6">0</span>
|
|
||||||
<span class="text-sm text-gray-500 dark:text-gray-400 absolute start-1/2 -translate-x-1/2 rtl:translate-x-1/2 -bottom-6">90</span>
|
|
||||||
<span class="text-sm text-gray-500 dark:text-gray-400 absolute end-0 -bottom-6">180</span>
|
|
||||||
</div>
|
|
||||||
<button class="btn btn-neutral btn-sm" on:click={sweep}>Sweep range</button>
|
|
||||||
</div>
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import SettingsCard from '$lib/components/SettingsCard.svelte';
|
import SettingsCard from '$lib/components/SettingsCard.svelte';
|
||||||
import type { ServoConfiguration, Servo } from '$lib/types/models';
|
import type { ServoConfiguration, Servo } from '$lib/types/models';
|
||||||
import ServoController from './servo.svelte';
|
|
||||||
import Spinner from '$lib/components/Spinner.svelte';
|
import Spinner from '$lib/components/Spinner.svelte';
|
||||||
|
|
||||||
import { socket } from '$lib/stores';
|
import { socket } from '$lib/stores';
|
||||||
@@ -11,11 +10,11 @@
|
|||||||
|
|
||||||
let isLoading = false;
|
let isLoading = false;
|
||||||
|
|
||||||
let active = false
|
let active = false;
|
||||||
|
|
||||||
let servoId = 0
|
let servoId = 0;
|
||||||
|
|
||||||
const throttler = new Throttler()
|
const throttler = new Throttler();
|
||||||
|
|
||||||
const sweep = (event: any) => {
|
const sweep = (event: any) => {
|
||||||
let channel = event.detail.channel;
|
let channel = event.detail.channel;
|
||||||
@@ -23,11 +22,11 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const activateServo = (event: any) => {
|
const activateServo = (event: any) => {
|
||||||
socket.sendEvent('servoState', {'active':1});
|
socket.sendEvent('servoState', { active: 1 });
|
||||||
};
|
};
|
||||||
|
|
||||||
const deactivateServo = (event: any) => {
|
const deactivateServo = (event: any) => {
|
||||||
socket.sendEvent('servoState', {'active':0});
|
socket.sendEvent('servoState', { active: 0 });
|
||||||
};
|
};
|
||||||
|
|
||||||
let pwm = 306;
|
let pwm = 306;
|
||||||
@@ -35,14 +34,21 @@
|
|||||||
const updatePWM = () => {
|
const updatePWM = () => {
|
||||||
throttler.throttle(() => {
|
throttler.throttle(() => {
|
||||||
socket.sendEvent('servoPWM', { servo_id: servoId, pwm });
|
socket.sendEvent('servoPWM', { servo_id: servoId, pwm });
|
||||||
}, 10)
|
}, 10);
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<SettingsCard collapsible={false}>
|
<SettingsCard collapsible={false}>
|
||||||
<MotorOutline slot="icon" class="lex-shrink-0 mr-2 h-6 w-6 self-end" />
|
<MotorOutline slot="icon" class="lex-shrink-0 mr-2 h-6 w-6 self-end" />
|
||||||
<span slot="title">Servo</span>
|
<span slot="title">Servo</span>
|
||||||
<input type="range" min="200" max="400" bind:value={pwm} on:input={updatePWM} class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700">
|
<input
|
||||||
|
type="range"
|
||||||
|
min="200"
|
||||||
|
max="400"
|
||||||
|
bind:value={pwm}
|
||||||
|
on:input={updatePWM}
|
||||||
|
class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
|
||||||
|
/>
|
||||||
|
|
||||||
{#if isLoading}
|
{#if isLoading}
|
||||||
<Spinner />
|
<Spinner />
|
||||||
@@ -51,7 +57,12 @@
|
|||||||
<h2 class="text-lg">General servo configuration</h2>
|
<h2 class="text-lg">General servo configuration</h2>
|
||||||
<span class="flex items-center gap-2">
|
<span class="flex items-center gap-2">
|
||||||
<label for="servoId">Servo active{servoId}</label>
|
<label for="servoId">Servo active{servoId}</label>
|
||||||
<input type="checkbox" class="toggle" bind:checked={active} on:change={active ? deactivateServo : activateServo}>
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
class="toggle"
|
||||||
|
bind:checked={active}
|
||||||
|
on:change={active ? deactivateServo : activateServo}
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -104,6 +104,13 @@ void ESP32SvelteKit::setupServer() {
|
|||||||
return _apService.endpoint.handleStateUpdate(request, json);
|
return _apService.endpoint.handleStateUpdate(request, json);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// servo
|
||||||
|
_server->on("/api/servo/config", HTTP_GET,
|
||||||
|
[this](PsychicRequest *request) { return _servoController.endpoint.getState(request); });
|
||||||
|
_server->on("/api/servo/config", HTTP_POST, [this](PsychicRequest *request, JsonVariant &json) {
|
||||||
|
return _servoController.endpoint.handleStateUpdate(request, json);
|
||||||
|
});
|
||||||
|
|
||||||
#ifdef EMBED_WWW
|
#ifdef EMBED_WWW
|
||||||
ESP_LOGV("ESP32SvelteKit", "Registering routes from PROGMEM static resources");
|
ESP_LOGV("ESP32SvelteKit", "Registering routes from PROGMEM static resources");
|
||||||
WWWData::registerRoutes([&](const String &uri, const String &contentType, const uint8_t *content, size_t len) {
|
WWWData::registerRoutes([&](const String &uri, const String &contentType, const uint8_t *content, size_t len) {
|
||||||
|
|||||||
@@ -11,3 +11,4 @@
|
|||||||
#define DEVICE_CONFIG_FILE "/config/peripheral.json"
|
#define DEVICE_CONFIG_FILE "/config/peripheral.json"
|
||||||
#define SECURITY_SETTINGS_FILE "/config/securitySettings.json"
|
#define SECURITY_SETTINGS_FILE "/config/securitySettings.json"
|
||||||
#define WIFI_SETTINGS_FILE "/config/wifiSettings.json"
|
#define WIFI_SETTINGS_FILE "/config/wifiSettings.json"
|
||||||
|
#define SERVO_SETTINGS_FILE "/config/servoSettings.json"
|
||||||
@@ -7,17 +7,78 @@
|
|||||||
#include <HttpEndpoint.h>
|
#include <HttpEndpoint.h>
|
||||||
#include <SecurityManager.h>
|
#include <SecurityManager.h>
|
||||||
#include <StatefulService.h>
|
#include <StatefulService.h>
|
||||||
|
#include <stateful_service_endpoint.h>
|
||||||
#include <MathUtils.h>
|
#include <MathUtils.h>
|
||||||
#include <Timing.h>
|
#include <Timing.h>
|
||||||
|
|
||||||
#define EVENT_SERVO_CONFIGURATION_SETTINGS "servoPWM"
|
#define EVENT_SERVO_CONFIGURATION_SETTINGS "servoPWM"
|
||||||
#define EVENT_SERVO_STATE "servoState"
|
#define EVENT_SERVO_STATE "servoState"
|
||||||
|
|
||||||
class ServoController {
|
typedef struct {
|
||||||
|
float centerPwm;
|
||||||
|
float direction;
|
||||||
|
float centerAngle;
|
||||||
|
float conversion;
|
||||||
|
const char *name;
|
||||||
|
// bool enable;
|
||||||
|
} servo_settings_t;
|
||||||
|
|
||||||
|
class ServoSettings {
|
||||||
|
public:
|
||||||
|
servo_settings_t servos[12] = {
|
||||||
|
{306, -1, 0, 2.2, "Servo1"}, {306, 1, -45, 2.1055555, "Servo2"}, {306, 1, 90, 1.96923, "Servo3"},
|
||||||
|
{306, -1, 0, 2.2, "Servo4"}, {306, -1, 45, 2.1055555, "Servo5"}, {306, -1, -90, 1.96923, "Servo6"},
|
||||||
|
{306, 1, 0, 2.2, "Servo7"}, {306, 1, -45, 2.1055555, "Servo8"}, {306, 1, 90, 1.96923, "Servo9"},
|
||||||
|
{306, 1, 0, 2.2, "Servo10"}, {306, -1, 45, 2.1055555, "Servo11"}, {306, -1, -90, 1.96923, "Servo12"}};
|
||||||
|
|
||||||
|
static void read(ServoSettings &settings, JsonObject &root) {
|
||||||
|
JsonArray servos = root["servos"].to<JsonArray>();
|
||||||
|
|
||||||
|
for (auto &servo : settings.servos) {
|
||||||
|
JsonObject newServo = servos.add<JsonObject>();
|
||||||
|
|
||||||
|
newServo["center_pwm"] = servo.centerPwm;
|
||||||
|
newServo["direction"] = servo.direction;
|
||||||
|
newServo["center_angle"] = servo.centerAngle;
|
||||||
|
newServo["conversion"] = servo.conversion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static StateUpdateResult update(JsonObject &root, ServoSettings &settings) {
|
||||||
|
if (root["servos"].is<JsonArray>()) {
|
||||||
|
JsonArray servosJson = root["servos"];
|
||||||
|
int i = 0;
|
||||||
|
for (auto servo : servosJson) {
|
||||||
|
JsonObject servoObject = servo.as<JsonObject>();
|
||||||
|
|
||||||
|
uint8_t servoId = i; // servoObject["id"].as<uint8_t>();
|
||||||
|
|
||||||
|
settings.servos[servoId].centerPwm = servoObject["center_pwm"].as<float>();
|
||||||
|
settings.servos[servoId].centerAngle = servoObject["center_angle"].as<float>();
|
||||||
|
settings.servos[servoId].direction = servoObject["direction"].as<float>();
|
||||||
|
settings.servos[servoId].conversion = servoObject["conversion"].as<float>();
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ESP_LOGI("ServoController", "Updating servo data");
|
||||||
|
|
||||||
|
return StateUpdateResult::CHANGED;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
class ServoController : public StatefulService<ServoSettings> {
|
||||||
public:
|
public:
|
||||||
ServoController(PsychicHttpServer *server, FS *fs, SecurityManager *securityManager, Peripherals *peripherals,
|
ServoController(PsychicHttpServer *server, FS *fs, SecurityManager *securityManager, Peripherals *peripherals,
|
||||||
EventSocket *socket)
|
EventSocket *socket)
|
||||||
: _server(server), _securityManager(securityManager), _peripherals(peripherals), _socket(socket) {}
|
: _server(server),
|
||||||
|
_securityManager(securityManager),
|
||||||
|
_peripherals(peripherals),
|
||||||
|
_socket(socket),
|
||||||
|
endpoint(ServoSettings::read, ServoSettings::update, this),
|
||||||
|
_fsPersistence(ServoSettings::read, ServoSettings::update, this, &ESPFS, SERVO_SETTINGS_FILE) {
|
||||||
|
_fsPersistence.readFromFS();
|
||||||
|
}
|
||||||
|
|
||||||
void begin() {
|
void begin() {
|
||||||
_socket->onEvent(EVENT_SERVO_CONFIGURATION_SETTINGS,
|
_socket->onEvent(EVENT_SERVO_CONFIGURATION_SETTINGS,
|
||||||
@@ -61,8 +122,9 @@ class ServoController {
|
|||||||
void updateServoState() {
|
void updateServoState() {
|
||||||
for (int i = 0; i < 12; i++) {
|
for (int i = 0; i < 12; i++) {
|
||||||
angles[i] = lerp(angles[i], target_angles[i], 0.2);
|
angles[i] = lerp(angles[i], target_angles[i], 0.2);
|
||||||
float angle = dir[i] * angles[i] + center_angle_deg[i];
|
auto &servo = _state.servos[i];
|
||||||
uint16_t pwm = angle * servo_conversion[i] + center[i];
|
float angle = servo.direction * angles[i] + servo.centerAngle;
|
||||||
|
uint16_t pwm = angle * servo.conversion + servo.centerPwm;
|
||||||
if (pwm < 125 || pwm > 600) {
|
if (pwm < 125 || pwm > 600) {
|
||||||
ESP_LOGE("ServoController", "Servo %d, Invalid PWM value %d", i, pwm);
|
ESP_LOGE("ServoController", "Servo %d, Invalid PWM value %d", i, pwm);
|
||||||
continue;
|
continue;
|
||||||
@@ -75,11 +137,14 @@ class ServoController {
|
|||||||
EXECUTE_EVERY_N_MS(ServoInterval, { updateServoState(); });
|
EXECUTE_EVERY_N_MS(ServoInterval, { updateServoState(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StatefulHttpEndpoint<ServoSettings> endpoint;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
PsychicHttpServer *_server;
|
PsychicHttpServer *_server;
|
||||||
SecurityManager *_securityManager;
|
SecurityManager *_securityManager;
|
||||||
Peripherals *_peripherals;
|
Peripherals *_peripherals;
|
||||||
EventSocket *_socket;
|
EventSocket *_socket;
|
||||||
|
FSPersistence<ServoSettings> _fsPersistence;
|
||||||
|
|
||||||
bool is_active {true};
|
bool is_active {true};
|
||||||
constexpr static int ServoInterval = 2;
|
constexpr static int ServoInterval = 2;
|
||||||
|
|||||||
+28060
-28213
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user