Skip to content

Commit

Permalink
Automatic reboot on a schedule (#86)
Browse files Browse the repository at this point in the history
  • Loading branch information
mariusandra authored May 11, 2024
1 parent 9242e2a commit 6521d20
Show file tree
Hide file tree
Showing 10 changed files with 186 additions and 21 deletions.
4 changes: 2 additions & 2 deletions backend/app/api/frames.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ def api_frame_update(id: int):
frame = Frame.query.get_or_404(id)
fields = ['scenes', 'name', 'frame_host', 'frame_port', 'frame_access_key', 'frame_access', 'ssh_user', 'ssh_pass', 'ssh_port', 'server_host',
'server_port', 'server_api_key', 'width', 'height', 'rotate', 'color', 'interval', 'metrics_interval', 'log_to_file',
'scaling_mode', 'device', 'debug']
'scaling_mode', 'device', 'debug', 'reboot', 'control_code']
defaults = {'frame_port': 8787, 'ssh_port': 22}
try:
payload = request.json
Expand All @@ -188,7 +188,7 @@ def api_frame_update(id: int):
value = float(value)
elif field in ['debug']:
value = value == 'true' or value is True
elif field in ['scenes']:
elif field in ['scenes', 'reboot', 'control_code']:
if isinstance(value, str):
value = json.loads(value) if value is not None else None
setattr(frame, field, value)
Expand Down
4 changes: 4 additions & 0 deletions backend/app/models/frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ class Frame(db.Model):
log_to_file = db.Column(db.String(256), nullable=True)
debug = db.Column(db.Boolean, nullable=True)
last_log_at = db.Column(db.DateTime, nullable=True)
reboot = db.Column(JSON, nullable=True)
control_code = db.Column(JSON, nullable=True)
# apps
apps = db.Column(JSON, nullable=True)
scenes = db.Column(JSON, nullable=True)
Expand Down Expand Up @@ -77,6 +79,8 @@ def to_dict(self):
'scenes': self.scenes,
'last_log_at': self.last_log_at.replace(tzinfo=timezone.utc).isoformat() if self.last_log_at else None,
'log_to_file': self.log_to_file,
'reboot': self.reboot,
'control_code': self.control_code,
}

def new_frame(name: str, frame_host: str, server_host: str, device: Optional[str] = None, interval: Optional[float] = None) -> Frame:
Expand Down
10 changes: 10 additions & 0 deletions backend/app/tasks/deploy_frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,16 @@ def install_if_necessary(package: str, raise_on_error = True) -> int:
# # disable swap while we're at it
# exec_command(frame, ssh, "sudo systemctl disable dphys-swapfile.service")

if frame.reboot and frame.reboot.get('enabled') == 'true':
cron_schedule = frame.reboot.get('crontab', '0 0 * * *')
if frame.reboot.get('type') == 'raspberry':
crontab = f"{cron_schedule} root /sbin/shutdown -r now"
else:
crontab = f"{cron_schedule} root systemctl restart frameos.service"
exec_command(frame, ssh, f"echo '{crontab}' | sudo tee /etc/cron.d/frameos-reboot")
else:
exec_command(frame, ssh, "sudo rm -f /etc/cron.d/frameos-reboot")

# restart
exec_command(frame, ssh, "sudo systemctl daemon-reload")
exec_command(frame, ssh, "sudo systemctl enable frameos.service")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""reboot and control code
Revision ID: 8cad2df43b45
Revises: 1e2acb9652e8
Create Date: 2024-05-11 23:16:10.087724
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import sqlite

# revision identifiers, used by Alembic.
revision = '8cad2df43b45'
down_revision = '1e2acb9652e8'
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('frame', schema=None) as batch_op:
batch_op.add_column(sa.Column('reboot', sqlite.JSON(), nullable=True))
batch_op.add_column(sa.Column('control_code', sqlite.JSON(), nullable=True))

# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('frame', schema=None) as batch_op:
batch_op.drop_column('control_code')
batch_op.drop_column('reboot')

# ### end Alembic commands ###
14 changes: 7 additions & 7 deletions frontend/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 frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"copy-to-clipboard": "^3.3.3",
"fast-deep-equal": "^3.1.3",
"kea": "^3.1.6",
"kea-forms": "^3.1.1",
"kea-forms": "^3.2.0",
"kea-loaders": "^3.0.1",
"kea-router": "^3.1.4",
"kea-subscriptions": "^3.0.0",
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/scenes/frame/frameLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ const FRAME_KEYS: (keyof FrameType)[] = [
'scenes',
'debug',
'log_to_file',
'reboot',
'control_code',
]

function cleanBackgroundColor(color: string): string {
Expand Down
25 changes: 25 additions & 0 deletions frontend/src/scenes/frame/panels/FrameDetails/FrameDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,31 @@ export function FrameDetails({ className }: DetailsProps) {
<td className="text-blue-200 text-right">Log to file:</td>
<td className="break-words">{frame.log_to_file || <em>disabled</em>}</td>
</tr>
<tr>
<td className="text-blue-200 text-right">Reboot:</td>
<td className="break-words">
{frame.reboot?.enabled === 'true' ? (
<>
{String(frame.reboot?.type)} at {String(frame.reboot?.crontab)}
</>
) : (
'disabled'
)}
</td>
</tr>
{/* <tr>
<td className="text-blue-200 text-right">QR control code:</td>
<td className="break-words">
{frame.control_code?.enabled === 'true' ? (
<>
{String(frame.control_code?.position)}, size: {String(frame.control_code?.size)}, padding:{' '}
{String(frame.control_code?.padding)}
</>
) : (
'disabled'
)}
</td>
</tr> */}
<tr>
<td className="text-blue-200 text-right">Debug logging:</td>
<td className="break-words">{frame.debug ? 'enabled' : 'disabled'}</td>
Expand Down
101 changes: 90 additions & 11 deletions frontend/src/scenes/frame/panels/FrameSettings/FrameSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useActions, useValues } from 'kea'
import clsx from 'clsx'
import { Button } from '../../../../components/Button'
import { framesModel } from '../../../../models/framesModel'
import { Form } from 'kea-forms'
import { Form, Group } from 'kea-forms'
import { TextInput } from '../../../../components/TextInput'
import { Select } from '../../../../components/Select'
import { frameLogic } from '../../frameLogic'
Expand All @@ -16,7 +16,7 @@ export interface FrameSettingsProps {
}

export function FrameSettings({ className }: FrameSettingsProps) {
const { frameId, frame, frameFormTouches } = useValues(frameLogic)
const { frameId, frame, frameForm, frameFormTouches } = useValues(frameLogic)
const { touchFrameFormField, setFrameFormValues } = useActions(frameLogic)
const { deleteFrame } = useActions(framesModel)

Expand Down Expand Up @@ -281,16 +281,95 @@ export function FrameSettings({ className }: FrameSettingsProps) {
placeholder="e.g. /srv/frameos/logs/frame-{date}.log"
required
/>
</Field>{' '}
<Field name="debug" label="Debug logging (noisy)">
<Select
name="debug"
options={[
{ value: 'false', label: 'Disabled' },
{ value: 'true', label: 'Enabled' },
]}
/>
</Field>
<Group name="reboot">
<Field name="enabled" label="Automatic reboot">
<Select
name="enabled"
options={[
{ value: 'false', label: 'Disabled' },
{ value: 'true', label: 'Enabled' },
]}
/>
</Field>
{String(frameForm.reboot?.enabled) === 'true' && (
<div className="pl-4 space-y-4">
<Field name="crontab" label="Reboot time">
<Select
name="crontab"
options={[
{ value: '0 0 * * *', label: '00:00' },
{ value: '1 0 * * *', label: '01:00' },
{ value: '2 0 * * *', label: '02:00' },
{ value: '3 0 * * *', label: '03:00' },
{ value: '4 0 * * *', label: '04:00' },
{ value: '5 0 * * *', label: '05:00' },
{ value: '6 0 * * *', label: '06:00' },
{ value: '7 0 * * *', label: '07:00' },
{ value: '8 0 * * *', label: '08:00' },
{ value: '9 0 * * *', label: '09:00' },
{ value: '10 0 * * *', label: '10:00' },
{ value: '11 0 * * *', label: '11:00' },
{ value: '12 0 * * *', label: '12:00' },
{ value: '13 0 * * *', label: '13:00' },
{ value: '14 0 * * *', label: '14:00' },
{ value: '15 0 * * *', label: '15:00' },
{ value: '16 0 * * *', label: '16:00' },
{ value: '17 0 * * *', label: '17:00' },
{ value: '18 0 * * *', label: '18:00' },
{ value: '19 0 * * *', label: '19:00' },
{ value: '20 0 * * *', label: '20:00' },
{ value: '21 0 * * *', label: '21:00' },
{ value: '22 0 * * *', label: '22:00' },
{ value: '23 0 * * *', label: '23:00' },
]}
/>
</Field>
<Field name="type" label="What to reboot">
<Select
name="type"
options={[
{ value: 'frameos', label: 'FrameOS' },
{ value: 'raspberry', label: 'System reboot' },
]}
/>
</Field>
</div>
)}
</Group>
{/* <Group name="control_code">
<Field name="enabled" label="QR Control Code">
<Select
name="enabled"
options={[
{ value: 'false', label: 'Disabled' },
{ value: 'true', label: 'Enabled' },
]}
/>
</Field>
{String(frameForm.control_code?.enabled) === 'true' && (
<div className="pl-4 space-y-4">
<Field name="position" label="Position">
<Select
name="position"
options={[
{ value: 'top-left', label: 'Top Left' },
{ value: 'top-right', label: 'Top Right' },
{ value: 'bottom-left', label: 'Bottom Left' },
{ value: 'bottom-right', label: 'Bottom Right' },
{ value: 'center', label: 'Center' },
]}
/>
</Field>
<Field name="size" label="Size of each square in pixels">
<TextInput name="size" placeholder="2" />
</Field>
<Field name="padding" label="Padding in pixels">
<TextInput name="padding" placeholder="0" />
</Field>
</div>
)}
</Group> */}
</Form>
</>
)}
Expand Down
11 changes: 11 additions & 0 deletions frontend/src/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,17 @@ export interface FrameType {
debug?: boolean
last_log_at?: string
log_to_file?: string
reboot?: {
enabled?: 'true' | 'false'
crontab?: string
type?: 'frameos' | 'raspberry'
}
control_code?: {
enabled?: 'true' | 'false'
position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'center'
size?: string
padding?: string
}
}

export interface TemplateType {
Expand Down

0 comments on commit 6521d20

Please sign in to comment.