Skip to content

Commit

Permalink
Custom variables (#83)
Browse files Browse the repository at this point in the history
* re-arrange config

* load Custom Fields

* proper headers for caching

* update custom variables

* mark version config to be removed

* add feedback

* clear commit  history

* bump: v4.5.0

* simplify `variablesFromCustomFields`
  • Loading branch information
alex-Arc authored Oct 25, 2024
1 parent be6cb7a commit 88e205e
Show file tree
Hide file tree
Showing 11 changed files with 197 additions and 53 deletions.
2 changes: 1 addition & 1 deletion companion/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"name": "getontime-ontime",
"shortname": "ontime",
"description": "Companion module for ontime",
"version": "4.4.0",
"version": "4.5.0",
"license": "MIT",
"repository": "git+https://github.com/bitfocus/companion-module-getontime-ontime.git",
"bugs": "https://github.com/bitfocus/companion-module-getontime-ontime/issues",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "getontime-ontime",
"version": "4.4.0",
"version": "4.5.0",
"main": "/dist/index.js",
"license": "MIT",
"prettier": "@companion-module/tools/.prettierrc.json",
Expand Down
38 changes: 25 additions & 13 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ export interface OntimeConfig {
host: string
port: string
ssl: boolean
version: string
version: string //TODO: remove
refetchEvents: boolean
customToVariable: boolean
reconnect: boolean
reconnectInterval: number
}
Expand Down Expand Up @@ -35,7 +36,7 @@ export function GetConfigFields(): SomeCompanionConfigField[] {
type: 'textinput',
default: '4001',
required: true,
width: 6,
width: 3,
regex: Regex.PORT,
tooltip: 'Ontime server port. Default is 4001',
},
Expand All @@ -44,23 +45,16 @@ export function GetConfigFields(): SomeCompanionConfigField[] {
id: 'ssl',
type: 'checkbox',
default: false,
width: 2,
tooltip: 'Use SSL to connect to the Ontime server.',
},
{
label: 'Refetch events',
id: 'refetchEvents',
type: 'checkbox',
default: true,
width: 3,
tooltip: 'Whether Companion should keep the rundown updated with Ontime by refetching on change.',
tooltip: 'Use SSL to connect to the Ontime server.',
},
//New line
{
label: 'Reconnect',
id: 'reconnect',
type: 'checkbox',
default: true,
width: 3,
width: 4,
tooltip: 'Chose if you want Companion to try to reconnect to ontime when the connection is lost.',
},
{
Expand All @@ -69,10 +63,28 @@ export function GetConfigFields(): SomeCompanionConfigField[] {
type: 'number',
min: 1,
max: 60,
default: 5,
default: 8,
width: 4,
isVisible: (config) => config.reconnect === true,
tooltip: 'The interval in seconds between each reconnect attempt.',
},
//New line
{
label: 'Refetch events',
id: 'refetchEvents',
type: 'checkbox',
default: true,
width: 12,
tooltip: 'Whether Companion should keep the rundown updated with Ontime by refetching on change.',
},
//New line
{
label: 'Custom variables',
id: 'customToVariable',
type: 'checkbox',
default: true,
width: 12,
tooltip: 'Whether Ontime custom fields should be written to variables.',
},
]
}
2 changes: 2 additions & 0 deletions src/enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ export enum feedbackId {

RundownOffset = 'rundownOffset',

CustomFieldsValue = 'customFieldsValue',

AuxTimerPlayback = 'auxTimerPlayback',
AuxTimerNegative = 'auxTimerNegativePlayback',
}
Expand Down
23 changes: 21 additions & 2 deletions src/utilities.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { DropdownChoice } from '@companion-module/base'
import { OntimeEvent, RuntimeStore, SimpleTimerState } from './v3/ontime-types.js'
import { CompanionVariableValues, DropdownChoice } from '@companion-module/base'
import { EventCustomFields, OntimeEvent, RuntimeStore, SimpleTimerState } from './v3/ontime-types.js'
import { OntimeV3 } from './v3/ontimev3.js'

export const joinTime = (...args: string[]) => args.join(':')
Expand Down Expand Up @@ -99,3 +99,22 @@ export function findPreviousPlayableEvent(ontime: OntimeV3): OntimeEvent | null

return null
}

export function variablesFromCustomFields(
ontime: OntimeV3,
postFix: string,
val: EventCustomFields | undefined
): CompanionVariableValues {
const companionVariableValues: CompanionVariableValues = {}
if (typeof val === 'undefined') {
Object.keys(ontime.customFields).forEach((key) => {
companionVariableValues[`${key}_Custom${postFix}`] = undefined
})
return companionVariableValues
}

Object.keys(ontime.customFields).forEach((key) => {
companionVariableValues[`${key}_Custom${postFix}`] = val[key]
})
return companionVariableValues
}
80 changes: 49 additions & 31 deletions src/v3/connection.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { InputValue, InstanceStatus } from '@companion-module/base'
import { OnTimeInstance } from '..'
import Websocket from 'ws'
import { findPreviousPlayableEvent, msToSplitTime, sanitizeHost } from '../utilities'
import { findPreviousPlayableEvent, msToSplitTime, sanitizeHost, variablesFromCustomFields } from '../utilities'
import { feedbackId, variableId } from '../enums'
import {
CurrentBlockState,
Expand Down Expand Up @@ -155,11 +155,15 @@ export function connect(self: OnTimeInstance, ontime: OntimeV3): void {
const updateEventNow = (val: OntimeEvent | null) => {
ontime.state.eventNow = val
self.setVariableValues({
[variableId.TitleNow]: val?.title ?? '',
[variableId.NoteNow]: val?.note ?? '',
[variableId.CueNow]: val?.cue ?? '',
[variableId.IdNow]: val?.id ?? '',
[variableId.TitleNow]: val?.title,
[variableId.NoteNow]: val?.note,
[variableId.CueNow]: val?.cue,
[variableId.IdNow]: val?.id,
})
if (self.config.customToVariable) {
self.setVariableValues(variablesFromCustomFields(ontime, 'Now', val?.custom))
self.checkFeedbacks(feedbackId.CustomFieldsValue)
}
}

const updateEventPrevious = (val: OntimeEvent | null) => {
Expand All @@ -169,6 +173,9 @@ export function connect(self: OnTimeInstance, ontime: OntimeV3): void {
[variableId.CuePrevious]: val?.cue ?? '',
[variableId.IdPrevious]: val?.id ?? '',
})
if (self.config.customToVariable) {
self.setVariableValues(variablesFromCustomFields(ontime, 'Previous', val?.custom))
}
}

const updateEventNext = (val: OntimeEvent | null) => {
Expand All @@ -179,6 +186,10 @@ export function connect(self: OnTimeInstance, ontime: OntimeV3): void {
[variableId.CueNext]: val?.cue ?? '',
[variableId.IdNext]: val?.id ?? '',
})
if (self.config.customToVariable) {
self.setVariableValues(variablesFromCustomFields(ontime, 'Next', val?.custom))
self.checkFeedbacks(feedbackId.CustomFieldsValue)
}
}

const updateCurrentBlock = (val: CurrentBlockState) => {
Expand Down Expand Up @@ -207,7 +218,7 @@ export function connect(self: OnTimeInstance, ontime: OntimeV3): void {
self.checkFeedbacks(feedbackId.AuxTimerNegative, feedbackId.AuxTimerPlayback)
}

ws.onmessage = (event: any) => {
ws.onmessage = async (event: any) => {
try {
const data = JSON.parse(event.data)
const { type, payload } = data
Expand Down Expand Up @@ -272,15 +283,22 @@ export function connect(self: OnTimeInstance, ontime: OntimeV3): void {
self.log('debug', version)
if (version.at(0) === '3') {
if (Number(version.at(1)) < 6) {
self.updateStatus(InstanceStatus.BadConfig, 'Ontime version is too old (required >3.6.0) some features are not available')
self.updateStatus(
InstanceStatus.BadConfig,
'Ontime version is too old (required >3.6.0) some features are not available'
)
} else {
self.updateStatus(InstanceStatus.Ok, payload)
}
fetchAllEvents(self, ontime).then(() => {
self.init_actions()
const prev = findPreviousPlayableEvent(ontime)
updateEventPrevious(prev)
})
await fetchCustomFields(self, ontime)
await fetchAllEvents(self, ontime)
self.init_actions()
self.init_feedbacks()
const prev = findPreviousPlayableEvent(ontime)
updateEventPrevious(prev)
if (self.config.customToVariable) {
self.setVariableDefinitions(ontime.getVariables(true))
}
} else {
self.updateStatus(InstanceStatus.ConnectionFailure, 'Unsupported version: see log')
self.log(
Expand All @@ -292,14 +310,17 @@ export function connect(self: OnTimeInstance, ontime: OntimeV3): void {
break
}
case 'ontime-refetch': {
if (self.config.refetchEvents === false) {
break
}
fetchAllEvents(self, ontime).then(() => {
self.init_actions()
if (self.config.refetchEvents) {
await fetchAllEvents(self, ontime)
const prev = findPreviousPlayableEvent(ontime)
updateEventPrevious(prev)
})
self.init_actions()
}
const change = await fetchCustomFields(self, ontime)
if (change && self.config.customToVariable) {
self.setVariableDefinitions(ontime.getVariables(true))
self.init_feedbacks()
}
break
}
}
Expand Down Expand Up @@ -330,15 +351,15 @@ export function socketSendJson(type: string, payload?: InputValue | object): voi

let rundownEtag: string = ''

export async function fetchAllEvents(self: OnTimeInstance, ontime: OntimeV3): Promise<void> {
async function fetchAllEvents(self: OnTimeInstance, ontime: OntimeV3): Promise<void> {
const prefix = self.config.ssl ? 'https' : 'http'
const host = sanitizeHost(self.config.host)

self.log('debug', 'fetching events from ontime')
try {
const response = await fetch(`${prefix}://${host}:${self.config.port}/data/rundown`, {
method: 'GET',
headers: { Etag: rundownEtag },
headers: { 'if-none-match': rundownEtag, 'cache-control': '3600', pragma: '' },
})
if (!response.ok) {
ontime.events = []
Expand All @@ -361,32 +382,29 @@ export async function fetchAllEvents(self: OnTimeInstance, ontime: OntimeV3): Pr
}

let customFieldsEtag: string = ''
let customFieldsTimeout: NodeJS.Timeout

export async function fetchCustomFields(self: OnTimeInstance, ontime: OntimeV3): Promise<void> {
//TODO: this might need to be updated on an interval
async function fetchCustomFields(self: OnTimeInstance, ontime: OntimeV3): Promise<boolean> {
const prefix = self.config.ssl ? 'https' : 'http'
const host = sanitizeHost(self.config.host)

clearTimeout(customFieldsTimeout)
if (self.config.refetchEvents) {
customFieldsTimeout = setTimeout(() => fetchCustomFields(self, ontime), 60000)
}
self.log('debug', 'fetching custom-fields from ontime')
try {
const response = await fetch(`${prefix}://${host}:${self.config.port}/data/custom-fields`, {
method: 'GET',
headers: { Etag: customFieldsEtag },
headers: { 'if-none-match': customFieldsEtag, 'cache-control': '3600', pragma: '' },
})
if (response.status === 304) {
return
self.log('debug', '304 -> nothing change custom fields')
return false
}
customFieldsEtag = response.headers.get('Etag') ?? ''
const data = (await response.json()) as CustomFields
ontime.customFields = data

self.init_actions()
return true
} catch (e: any) {
ontime.events = []
self.log('error', `unable to fetch events: ${e}`)
self.log('error', `unable to fetch custom fields: ${e}`)
return false
}
}
80 changes: 80 additions & 0 deletions src/v3/feedbacks/customFields.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { CompanionFeedbackDefinition } from '@companion-module/base'
import { OntimeV3 } from '../ontimev3'
import { feedbackId } from '../../enums'
import { ActiveBlue, White } from '../../assets/colours'

export function createCustomFieldsFeedbacks(ontime: OntimeV3): { [id: string]: CompanionFeedbackDefinition } {
const { customFields } = ontime
return {
[feedbackId.CustomFieldsValue]: {
type: 'boolean',
name: 'Custom Field',
description: 'Colour of indicator for rundown offset state',
defaultStyle: {
color: White,
bgcolor: ActiveBlue,
},
options: [
{
type: 'dropdown',
id: 'target',
label: 'Target',
choices: [
{ id: 'now', label: 'Current Event' },
{ id: 'next', label: 'Next Event' },
],
default: 'now',
},
{
type: 'dropdown',
id: 'field',
label: 'Custom Field',
choices: Object.entries(customFields).map(([key, field]) => {
return { id: key, label: field.label }
}),
default: '',
},
{
type: 'checkbox',
id: 'requireValue',
label: 'Match specific value',
default: false,
},
{
type: 'textinput',
id: 'match',
label: 'Value to match',
isVisible: (opts) => opts.requireValue === true,
},
],
learn: (action) => {
const target = action.options.target as string
const field = action.options.field as string
const fieldValue =
target === 'now' ? ontime.state.eventNow?.custom[field] : ontime.state.eventNext?.custom[field]
return { ...action.options, requireValue: true, match: fieldValue ?? '' }
},
callback: (feedback) => {
const target = feedback.options.target as string
const field = feedback.options.field as string
const requireValue = feedback.options.requireValue as string
const match = feedback.options.match as string
const fieldValue =
target === 'now' ? ontime.state.eventNow?.custom[field] : ontime.state.eventNext?.custom[field]

if (fieldValue === undefined || fieldValue === '') {
return false
}

if (!requireValue) {
return true
}

if (match === fieldValue) {
return true
}
return false
},
},
}
}
Loading

0 comments on commit 88e205e

Please sign in to comment.