Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v2.4 Feature - Time Clock Triggers - Scheduled Presets #163

Merged
merged 46 commits into from
Jul 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
82bb3e5
bump
Jbithell May 23, 2023
19fa8c0
TS Docs aren't actually provided as far as I can see
Jbithell Jun 3, 2023
c8ed799
Bump
Jbithell Jun 3, 2023
744e3a2
Merge branch 'main' into 150-feature-scheduled-macros
Jbithell Jun 5, 2023
7a30097
Add database structure
Jbithell Jun 5, 2023
ec409dc
Add edit page
Jbithell Jun 5, 2023
ee23c65
Add config option to macro preset
Jbithell Jun 10, 2023
9c1d6c6
Allow presets to be recalled over http
Jbithell Jun 10, 2023
22ff9d0
Bump version
Jbithell Jun 10, 2023
03b325c
Improve locked message, and fix logic error in web server
Jbithell Jun 10, 2023
3cfca8e
Basic work on time clock triggers
Jbithell Jun 10, 2023
f4378f9
Remove time clock triggers
Jbithell Jun 10, 2023
9d50faf
Lint
Jbithell Jun 10, 2023
6029748
Remove stats file
Jbithell Jun 10, 2023
0593722
Lint
Jbithell Jun 10, 2023
dfd2868
Update folder.ts
Jbithell Jun 10, 2023
80c7a01
Add badge showing preset type
Jbithell Jun 10, 2023
f5292a5
Add parent name to folders
Jbithell Jun 15, 2023
c7d2134
Add duplicate preset button
Jbithell Jun 15, 2023
0e497c6
Refactor folder icons to allow them to be used with other buttons
Jbithell Jun 15, 2023
7f0834c
Add preset icons, and make icons searchable
Jbithell Jun 15, 2023
8ae553a
Add more icons
Jbithell Jun 15, 2023
9434e36
Improve admin pin entry
Jbithell Jun 15, 2023
848c214
Update settings.json
Jbithell Jun 15, 2023
47e7a8f
Start UI
Jbithell Jun 15, 2023
b779322
Bump
Jbithell Jun 15, 2023
2044830
Fix imports
Jbithell Jun 15, 2023
051422e
Merge branch '160-feature-various' into 150-feature-time-clock-triggers
Jbithell Jun 15, 2023
95fd6ab
Add more ui
Jbithell Jun 15, 2023
d48f6f2
Merge branch 'main' into 150-feature-time-clock-triggers
Jbithell Jun 16, 2023
3abcf33
Remove webpack cache to fix database error in https://github.com/elec…
Jbithell Jun 16, 2023
b1c3861
Radically simplify icon file
Jbithell Jun 16, 2023
360a066
Basic UI
Jbithell Jun 16, 2023
aa85888
UI tweaks
Jbithell Jun 18, 2023
0b76940
Edit functionality complete
Jbithell Jun 18, 2023
f7e9f2f
Build out function
Jbithell Jun 18, 2023
b4c91a3
Update NavbarItem.tsx
Jbithell Jun 18, 2023
ab02e2b
Fix bugs
Jbithell Jun 18, 2023
6dddda3
Time clock trigger bugfixes
Jbithell Jun 18, 2023
c45200e
Clarify skip behaviour
Jbithell Jun 18, 2023
ece2863
Lint
Jbithell Jun 18, 2023
63a1209
Closes https://github.com/Paradise-Pi/ParadisePi/issues/164 by removi…
Jbithell Jun 18, 2023
fb686a9
Update index.ts
Jbithell Jul 8, 2023
507ca02
Add error handling to sampling mode
Jbithell Jul 14, 2023
64e32db
Add docs
Jbithell Jul 14, 2023
958b3b2
Merge branch 'main' into 150-feature-time-clock-triggers
Jbithell Jul 14, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ Please describe the changes
- [ ] Preset/Faders/Folders sort order is maintained
- [ ] Presets can be triggered by HTTP requests

### Preset Triggers

- [ ] Presets can be triggered by HTTP requests
- [ ] Time clock triggers recall presets successfully
- [ ] Time clock triggers do not recall when device locked

### Preset Types

#### sACN
Expand Down
4 changes: 4 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
"DEV_MODE": "true"
},
"console": "integratedTerminal",
"skipFiles": [
"<node_internals>/**/*.js",
"${workspaceFolder}/node_modules/**/*.js",
]
}
]
}
23 changes: 23 additions & 0 deletions docs/docs/user-guide/admin/scheduled-presets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
sidebar_position: 30
title: Scheduled Presets
---

The Scheduled Presets page allows you to define times of the day when presets are triggered automatically.

To start, use the plus button to add a new schedule. Next, select the days of the week you'd like the schedule to run on, and the time to run during those days. Finally, select the preset you'd like to run at that time, and click save.

To trigger a preset at multiple times during the day, simply add multiple schedules for that preset.

![scheduled presets page](@site/static/img/tutorial/admin/admin-scheduled-presets.png)

The cog icon on the right side of each preset allows you to edit the advanced settings for that preset.

![scheduled presets cog page](@site/static/img/tutorial/admin/admin-scheduled-presets-cog.png)

The advanced settings allow you to set the following:

- Notes: A description of why the schedule exists as a reminder in the administrator menu - this can help you differentiate between large numbers of schedules.
- Enabled: whether the schedule is enabled or not. If disabled, the schedule will not run.
- Run when locked: if the control panel is locked, the schedule will still run if this is enabled.
- Timeout: how many minutes to keep trying to trigger the preset for. If the preset is not triggered within this time, the schedule will be skipped for that day. This is particularly relevant if ParadisePi is powered off at the time the schedule is due to run, and later powered on - if the timeout plus the scheduled time is earlier than the time ParadisePi boots, then it will be skipped.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "paradisepi",
"version": "2.3.0",
"version": "2.4.0",
"description": "Raspberry Pi based sACN lighting/OSC sound facility control panel",
"main": ".webpack/main",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion src/api/config/configRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const configRouter = (
method: 'GET' | 'POST' | 'PUT' | 'DELETE',
payload: apiObject
): Promise<apiObject> => {
logger.debug('Preset router has a request', { path, method, payload })
logger.debug('Config router has a request', { path, method, payload })
return new Promise((resolve, reject) => {
if (method === 'POST') {
let restartE131 = false
Expand Down
15 changes: 8 additions & 7 deletions src/api/database.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { DatabasePreset, PresetRepository } from './../database/repository/preset'
import { DatabaseFolder, FolderRepository } from './../database/repository/folder'
import ip from 'ip'
import { version } from './../../package.json'
import { ConfigRepository } from './../database/repository/config'
import { DatabaseFader, FaderRepository } from './../database/repository/fader'
import { DatabaseFolder, FolderRepository } from './../database/repository/folder'
import { DatabasePreset, PresetRepository } from './../database/repository/preset'
import { DatabaseTimeClockTrigger, TimeClockTriggersRepository } from './../database/repository/timeClockTrigger'
import { getOperatingSystemName } from './about/operatingSystem/info'
import { version } from './../../package.json'
import { broadcast } from './broadcast'
import ip from 'ip'
import { DatabaseFader, FaderRepository } from './../database/repository/fader'

export interface Database {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down Expand Up @@ -42,10 +43,10 @@ export interface Database {
e131Frequency: number
e131FadeTime: number
e131Sampler_time: number
e131Sampler_effectMode: number
}
}
presets: Array<DatabasePreset>
timeClockTriggers: Array<DatabaseTimeClockTrigger>
folders: {
[key: number]: DatabaseFolder
}
Expand Down Expand Up @@ -91,10 +92,10 @@ export const createDatabaseObject = async (message: string): Promise<Database> =
e131Frequency: parseInt(await ConfigRepository.getItem('e131Frequency')),
e131FadeTime: parseInt(await ConfigRepository.getItem('e131FadeTime')),
e131Sampler_time: parseInt(await ConfigRepository.getItem('e131Sampler_time')),
e131Sampler_effectMode: parseInt(await ConfigRepository.getItem('e131Sampler_effectMode')),
},
},
presets: await PresetRepository.getAll(),
timeClockTriggers: await TimeClockTriggersRepository.getAll(),
folders: await FolderRepository.getAll(),
faders: await FaderRepository.getAll(),
}
Expand Down
6 changes: 3 additions & 3 deletions src/api/preset/presetRouter.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Preset } from './../../database/model/Preset'
import { DatabasePreset, PresetRepository } from './../../database/repository/preset'
import { createDatabaseObject, Database, sendDatabaseObject } from './../database'
import axios from 'axios'
import { parseJSON } from '../parseUserJson'
import { Preset } from './../../database/model/Preset'
import { ConfigRepository } from './../../database/repository/config'
import { DatabasePreset, PresetRepository } from './../../database/repository/preset'
import { Database, createDatabaseObject, sendDatabaseObject } from './../database'
/**
* This is a REST router for the preset API.
* @param path - The path requested by the original route requestor
Expand Down
15 changes: 10 additions & 5 deletions src/api/router.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { reboot } from './../electron/windowUtilities'
import { aboutRouter } from './about/aboutRouter'
import { createDatabaseObject } from './database'
import { presetRouter } from './preset/presetRouter'
import { folderRouter } from './folder/folderRouter'
import { configRouter } from './config/configRouter'
import { outputModulesRouter } from './outputModules/outputModulesRouter'
import { reboot } from './../electron/windowUtilities'
import { createDatabaseObject } from './database'
import { faderRouter } from './fader/faderRouter'
import { folderRouter } from './folder/folderRouter'
import { createImagesObject } from './images'
import { outputModulesRouter } from './outputModules/outputModulesRouter'
import { presetRouter } from './preset/presetRouter'
import { timeClockTriggersRouter } from './timeClockTriggers/timeClockTriggers'

/**
* This is a REST router that triages all requests and sends them to relevant routers
Expand Down Expand Up @@ -52,6 +53,10 @@ export const routeRequest = (
// {@link configRouter} - this router handles all about requests for the /config path
resolve(configRouter(pathArr.slice(1), method, payload))
break
case 'timeClockTriggers':
// {@link timeClockTriggers} - the timeClockTriggers router handles all about requests for the /timeClockTriggers path
resolve(timeClockTriggersRouter(pathArr.slice(1), method, payload))
break
case 'outputModules':
// {@link outputModulesRouter} - this router handles all about requests for the /outputModules path
resolve(outputModulesRouter(pathArr.slice(1), method, payload))
Expand Down
29 changes: 29 additions & 0 deletions src/api/timeClockTriggers/timeClockTriggers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Database, createDatabaseObject, sendDatabaseObject } from '../database'
import { DatabaseTimeClockTrigger, TimeClockTriggersRepository } from './../../database/repository/timeClockTrigger'
/**
* This is a REST router for the preset API.
* @param path - The path requested by the original route requestor
* @param method - The method requested by the original route requestor
* @param payload - Any payload sent
* @returns the retrieved response from the given route
* @throws an error if the requested route is not found
*/
export const timeClockTriggersRouter = (
path: Array<string>,
method: 'GET' | 'POST' | 'PUT' | 'DELETE',
payload: apiObject
): Promise<apiObject> => {
logger.debug('Time clock trigger router has a request', { path, method, payload })
return new Promise((resolve, reject) => {
if (method === 'PUT') {
return TimeClockTriggersRepository.setAllFromApp(payload as Array<DatabaseTimeClockTrigger>)
.then(() => {
return createDatabaseObject('updating all time clock triggers in bulk')
})
.then((response: Database) => {
sendDatabaseObject(response)
resolve({})
})
} else reject(new Error('Path not found'))
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import React, { useEffect } from 'react'
import { Group, ActionIcon, Button, Text, Checkbox, NumberInput } from '@mantine/core'
import { useForm } from '@mantine/form'
import { FaTrash } from '@react-icons/all-files/fa/FaTrash'
import { randomId } from '@mantine/hooks'
import { InputProps } from '../../../../InputProps'

interface Trigger {
time: string
enabled: boolean
timeout: number
countdownWarning: number
key: string
}
interface FormValues {
triggers: Array<Trigger>
}

export const TimeClockTriggersEditor = (props: InputProps) => {
const form = useForm<FormValues>({
initialValues: {
triggers: [],
},
})
useEffect(() => {
if (props.value !== null) {
const valueObject = JSON.parse(props.value) || {}
form.setValues({
triggers: valueObject.map((item: Trigger) => ({
time: item.time,
key: item.key,
})),
})
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [props.value])
return (
<>
<Group mt="xs">
<Text mt={'md'}>Time Clock Triggers</Text>
<Button
mt={'md'}
size="sm"
onClick={() =>
form.insertListItem('triggers', {
time: '',
enabled: true,
timeout: 60,
countdownWarning: 0,
key: randomId(),
})
}
>
Add Trigger
</Button>
{form.isDirty() ? (
<Button mt={'md'} size="sm" onClick={() => props.onChange(JSON.stringify(form.values.triggers))}>
Apply
</Button>
) : null}
</Group>
<Text size={'sm'}>Time Clock Triggers will recall this preset at the following times (if enabled)</Text>
{form.values.triggers.map((item, index) => (
<Group key={item.key} mt="xs">
<NumberInput
label="Trigger Time"
max={2359}
min={0}
parser={value =>
value.match(/\d(?=(?:\D*\d){0,3}$)/g) ? value.match(/\d(?=(?:\D*\d){0,3}$)/g).join('') : ''
}
formatter={value =>
!Number.isNaN(parseFloat(value))
? value.replace(/\b\d{1,4}\b/g, match => {
const paddedNumber = match.padStart(4, '0')
const hours = paddedNumber.slice(0, 2)
const minutes = paddedNumber.slice(2)
return `${hours}:${minutes}`
})
: ''
}
{...form.getInputProps(`triggers.${index}.time`)}
/>
<Checkbox
mt={'lg'}
size={'lg'}
label="Enabled"
{...form.getInputProps(`triggers.${index}.enabled`, { type: 'checkbox' })}
/>
<ActionIcon
mt={'lg'}
color="red"
variant="transparent"
onClick={() => form.removeListItem('triggers', index)}
>
<FaTrash />
</ActionIcon>
</Group>
))}

<Group position="center" mt="md"></Group>
</>
)
}
Loading