[{"server":null,"owner":null,"id":"2e173585-b2e2-4fcd-9a75-37d6c7ca9b2b","params":{"result":{"value":"","datetime":1699001378.0,"status":null,"version":0},"hash":{"value":"fvryB4Fz7c1wIrQQw+tb5w==","datetime":1725365459.2413833,"status":null,"version":0},"enabled":{"value":true,"datetime":1722010919.614,"status":null,"version":0},"error_text":{"value":"","datetime":1725346850.282,"status":null,"version":0},"type":{"value":"EgsScenario","datetime":1725365320.3497152,"status":null,"version":0},"settings":{"value":"{\n  \"main_loop_interval\": \"00:00:01\",\n  \"reset_param_timeout\": \"00:00:05\",\n  \"turn_off_StreetLighting_timeout\": \"00:00:30\",\n  \"reset_command_timeout\": \"00:00:05\",\n  \"max_command_repeats\": 10,\n  \"state_event_reasons\": {\n    \"Zone\": [\n      \"alarm_*\"\n    ],\n    \"VideoCamera\": [\n      \"alarm_*\"\n    ]\n  },\n  \"state_event_timeout\": \"00:00:30\",\n  \"play_siren\": {\n    \"reasons\": {\n      \"Sensor\": {\n        \"alarm_*\": \"2\"\n      }\n    },\n    \"paths\": [\n      \"Link/SirenBase\",\n      \"Link/Space/Link/SirenBase\",\n      \"Link/Zone/Link/SirenBase\"\n    ]\n  },\n  \"state_event_server\": \"server\",\n  \"turn_on_StreetLighting_filter\": {\n    \"BaseObject\": {\n      \"alarm_*\": true\n    },\n    \"VideoCamera\": {\n      \"alarm_*\": true,\n      \"alarm_reversBarrier\": false\n    }\n  },\n  \"turn_on_StreetLighting_schedule\": {\n    \"11/07 - 02/06\": \"17:17 - 07:45\",\n    \"02/06 - 05/07\": \"18:39 - 06:25\",\n    \"05/07 - 08/06\": \"20:01 - 05:06\",\n    \"08/06 - 11/07\": \"18:39 - 06:25\"\n  },\n  \"random_access_points\": [\n    \"bd93440a-c7c5-4d2d-a498-53d792e50b6a\",\n    \"fd8621de-9701-4dce-b0da-d4b81ab711f2\"\n  ]\n}","datetime":1722011744.733,"status":null,"version":0},"state":{"value":"ok.normal","datetime":1725346850.282,"status":null,"version":0},"script":{"value":"# имя: 'Misc Kerch'\r\n# описание: управление прожекторами для керченского моста\r\n# тип триггера: 'EgsScenario'\r\n# создан: 2017.04.24 15.55.51, Сельченков Н.Ю.\r\n# изменен: '2024.09.03 16.10.58', Сельченков Н.Ю.\r\n# подробности: https://redmine.integra-s.com:11000/projects/eilyacuario/wiki/Misc_Kerch\r\n\r\n### UTILS ###\r\n\r\nuse Newtonsoft.Json.JsonConvert from Newtonsoft.Json\r\nuse acuario2.utils.TextExtension\r\nuse acuario2.utils.DateTimeExtension\r\nuse System.Globalization.CultureInfo\r\nuse System.Collections.Concurrent.ConcurrentDictionary(BaseObject, string) as EventGroups\r\nuse System.Random\r\n\r\nlet json(obj as object) = JsonConvert.SerializeObject(obj)     \r\nlet shrink(str as string, threshold as int) = TextExtension.Shrink(str ?? \"\", threshold)\r\n\r\n### SETTINGS ###\r\n\r\nlet random = Random()\r\nlet forget_event_statuses = new [EventStatus.accepted, EventStatus.canceled, EventStatus.lost_event]\r\n\r\nuse json_schema\r\n`\r\n{\r\n    \"type\": \"object\",\r\n    \"properties\":\r\n    {\r\n        \"main_loop_interval\": { \"type\": \"string\", \"format\": \"TimeSpan\", \"default\": \"00:00:01\" },\r\n        \"reset_command_timeout\": { \"type\": \"string\", \"format\": \"TimeSpan\", \"default\": \"00:00:05\" },\r\n        \"turn_on_StreetLighting_filter\":      \r\n        { \r\n            \"type\": \"object\",\r\n            \"additionalProperties\": \r\n            { \r\n                \"type\": \"object\",\r\n                \"additionalProperties\": { \"type\": \"boolean\" } \r\n            }\r\n        },\r\n        \"turn_on_StreetLighting_schedule\":      \r\n        { \r\n            \"type\": \"object\",\r\n            \"additionalProperties\": \r\n            { \r\n                \"type\": \"string\",\r\n                \"default\": \"00:00 - 00:00\" \r\n            }\r\n        },        \r\n        \"turn_off_StreetLighting_timeout\": { \"type\": \"string\", \"format\": \"TimeSpan\", \"default\": \"00:00:30\" },\r\n        \"state_event_timeout\": { \"type\": \"string\", \"format\": \"TimeSpan\", \"default\": \"08:00:00\" },\r\n        \"state_event_reasons\":      \r\n        { \r\n            \"type\": \"object\",\r\n            \"additionalProperties\": \r\n            { \r\n                \"type\": \"array\",\r\n                \"items\": { \"type\": \"string\" }\r\n            }\r\n        },        \r\n        \"play_siren\":      \r\n        { \r\n            \"type\": \"object\",\r\n            \"properties\":\r\n            {\r\n                \"paths\": \r\n                { \r\n                    \"type\": \"array\",\r\n                    \"items\": { \"type\": \"string\" }\r\n                     \r\n                },\r\n                \"reasons\":      \r\n                { \r\n                    \"type\": \"object\",\r\n                    \"additionalProperties\": \r\n                    { \r\n                        \"type\": \"object\",\r\n                        \"additionalProperties\": { \"type\": \"string\" } \r\n                    }\r\n                }                  \r\n            }\r\n        },\r\n        \"state_event_server\": { \"type\": \"string\", \"default\": \"server\" }\r\n    }\r\n}\r\n` as SETTINGS\r\n\r\nprint this.settings\r\nlet settings     = SETTINGS(this.settings)\r\nlet event_groups = EventGroups()\r\n\r\n### SCHEDULES ###\r\n\r\nlet checkDateInterval(text as string) = \r\n    let parts = text.Split(new[\"-\"], 2, \"RemoveEmptyEntries\")    \r\n    let start = DateTime.ParseExact(parts[0].Trim(), \"MM/dd\", CultureInfo.InvariantCulture)\r\n    let @end  = DateTime.ParseExact(parts[1].Trim(), \"MM/dd\", CultureInfo.InvariantCulture)\r\n    if @end <= start then \r\n        if DateTime.Now < @end then start = start.AddYears(-1)\r\n        else @end = @end.AddYears(1)\r\n    end\r\n    let between = (DateTime.Now >= start) and (DateTime.Now < @end)\r\n#    print(\"DATE-INTERVAL\",start,@end,between)\r\n    between\r\nend\r\n\r\nlet checkTimeInterval(text as string) = \r\n    let parts = text.Split(new[\"-\"], 2, \"RemoveEmptyEntries\")    \r\n    let start = DateTime.ParseExact(parts[0].Trim(), \"HH:mm\", CultureInfo.InvariantCulture)\r\n    let @end  = DateTime.ParseExact(parts[1].Trim(), \"HH:mm\", CultureInfo.InvariantCulture)\r\n    if @end <= start then \r\n        if DateTime.Now < @end then start = start.AddDays(-1)\r\n        else @end = @end.AddDays(1)\r\n    end\r\n    let between = (DateTime.Now >= start) and (DateTime.Now < @end)\r\n#    print(\"TIME-INTERVAL\",start,@end,between)\r\n    between\r\nend\r\n   \r\n\r\nlet checkSchedule() =    \r\n    let schedules = settings.turn_on_StreetLighting_schedule\r\n    let interval  = from schedules where checkDateInterval(Key) and checkTimeInterval(Value) try first    \r\n    if interval is not null then print(\"INTERVAL\",interval.Key,interval.Value)\r\n    (interval isnt null) or (schedules is empty)\r\nend\r\n\r\n### COMMANDS ###\r\n\r\nlet set_command(item, command) = \r\n    let param           = item[\"command\"]\r\n    let timeout_elapsed = param.TimePassed > settings.reset_command_timeout \r\n    let prev_command    = param.Text\r\n    if prev_command is \"DEFAULT\" then \r\n        prev_command = param.Trace(1).Text     \r\n    let same_command = prev_command is string(command) \r\n    if timeout_elapsed or not same_command then \r\n        param.Set(command, null, DateTime.UtcNow)\r\nend\r\n\r\nlet inside_alarmed_zone(obj as BaseObject) = from obj.GetItemsLinkedTo(ZoneInputPin) of type Zone any it.State is \"alarm\"\r\n\r\nlet get_alarmed_zone_containing_sibling_lamp(lamp as StreetLighting) =\r\n    from   lamp.GetItemsLinkedTo(LogicInput)   of type StilSoftRelay\r\n    select many GetItemsLinkedTo(LogicOutput)  of type StreetLighting\r\n    select many GetItemsLinkedTo(ZoneInputPin) of type Zone\r\n    where it.State is \"alarm\" try first\r\nend\r\n\r\nlet outoffix (lamp as StreetLighting) = lamp.State is \"error\"\r\nlet active   (lamp as StreetLighting) = lamp.state is \"ok_active\"\r\nlet elapsed  (lamp as StreetLighting) = (lamp is active) and (lamp[\"state\"].TimePassed > settings.turn_off_StreetLighting_timeout)\r\n\r\nlet turn_on_StreetLighting() =\r\n    from graph.Values of type BaseObject do\r\n        let obj   = it\r\n        let state = it[\"state\"].Text                \r\n        from settings.turn_on_StreetLighting_filter where Key in obj.Types \r\n        select many (from Value where state like Key select Value)\r\n        order by it take 1 where it do \r\n            from obj.GetItemsLinkedTo(ZoneInputPin) of type Zone\r\n            select many GetItemsLinkedTo(ZoneOutputPin) of type StreetLighting \r\n            where (it isnt outoffix) and (it isnt active) do\r\n                additional_info = \"turn_on, \"..state..\", \"..obj\r\n                from GetItemsLinkedTo(LogicInput) of type StilSoftRelay do\r\n                    set_command(it, Stilsoft_relaySwitchCommand.turn_on)\r\n                now\r\n            now\r\n        now    \r\n    now\r\nend\r\n\r\nlet turn_off_StreetLighting() =     \r\n    from graph.Values of type StreetLighting \r\n    where (it isnt outoffix) and (it is elapsed) do\r\n        let alarmed_zone = get_alarmed_zone_containing_sibling_lamp(it)\r\n        if alarmed_zone is null then \r\n            additional_info = \"turn_off, \"..state..\", \"..it[\"state\"].DateTime\r\n            from GetItemsLinkedTo(LogicInput) of type StilSoftRelay do\r\n                set_command(it, Stilsoft_relaySwitchCommand.turn_off)\r\n            now            \r\n        else\r\n            additional_info = \"keep, \"..state..\", \"..alarmed_zone\r\n        end\r\n    now\r\nend\r\n\r\nlet on_StreetLighting_command(lamp as StreetLighting) = \r\n    from lamp.GetItemsLinkedTo(LogicInput) of type StilSoftRelay do\r\n        switch lamp.command\r\n            when \"turn_on\"  then set_command(it, Stilsoft_relaySwitchCommand.turn_on)\r\n            when \"turn_off\" then set_command(it, Stilsoft_relaySwitchCommand.turn_off)\r\n    now\r\n    lamp.command = \"DEFAULT\"\r\nend\r\n\r\nlet command_from_StreetLighting() = from graph.Values of type StreetLighting do on_StreetLighting_command(it) now\r\n\r\nlet on_StilSoftRelay_state(relay as StilSoftRelay) = \r\n    from relay.GetItemsLinkedTo(LogicOutput) of type StreetLighting do\r\n        switch relay.state\r\n            when \"ok_true\"         then state = \"ok_active\"\r\n            when \"ok_false\"        then state = \"ok_inactive\"\r\n            when \"error_defective\" then state = \"error_defective\"\r\n            else                        state = \"none_unknown\"     \r\n    now\r\nend\r\n\r\nlet state_from_StilSoftRelay() = from graph.Values of type StilSoftRelay do on_StilSoftRelay_state(it) now\r\n\r\nlet on_timer() =     \r\n    if checkSchedule() then turn_on_StreetLighting()\r\n    turn_off_StreetLighting()\r\n    command_from_StreetLighting()\r\n    state_from_StilSoftRelay()\r\nend\r\nthis.RunOnTimer(settings.main_loop_interval.TotalSeconds, on_timer) \r\n\r\n### CREATE ###\r\n\r\nlet get_server(obj as Object) = \r\n    let server = settings.state_event_server \r\n    try Guid(server) else try Guid(obj[server].Text) \r\n    else this.ServerId ?? Guid.Empty\r\nend\r\n\r\nlet create(obj as Object) =\r\n    let server  = get_server(obj) \r\n    print(\"CREATE OBJECT\", obj.Id, \"ON SERVER\", server)\r\n    let request = obj.ExportCreate(server)\r\n    let socket  = this.Module.GetSocket(server) \r\n    socket.Put(request)\r\nend\r\n\r\nlet update(obj as Object) =\r\n    let server  = get_server(obj) \r\n    print(\"UPDATE OBJECT\", obj.Id, \"ON SERVER\", server)\r\n    let request = obj.ExportUpdate(server) \r\n    let socket  = this.Module.GetSocket(server) \r\n    socket.Put(request)\r\nend\r\n\r\n### EVENTS ###\r\n\r\nuse acuario2.client.StateEvent as StateEventImpl\r\n\r\nlet same_as(event1 as StateEvent, event2 as StateEvent) = \r\n    (event1.owner is event2.owner) and (event1.state is event2.state) \r\nend    \r\n\r\nlet already_remembered(event as StateEvent) = \r\n    let same = from graph.Events of type StateEvent where it is same_as event try first \r\n    if same isnt null then print(\"ALREADY SENT\", string(same.Id), same.owner, same.name, same.state, same.datetime)\r\n    same isnt null\r\nend\r\n\r\nlet old(event as StateEvent) = DateTime.UtcNow - event.datetime > settings.state_event_timeout\r\n\r\nlet remember_event(event as StateEventImpl) = \r\n    print(\"REMEBER EVENT\", event.Id, event.owner, event.name, event.state, event.datetime)\r\n    graph.Remember(event)\r\nend\r\n\r\nlet forget_old_events() =\r\n    from graph.Events of type StateEvent where it is old do\r\n        print(\"FORGET OLD EVENT\", Id, owner, name, state, datetime)\r\n        graph.Forget(Id)\r\n    now\r\nend\r\n\r\nlet forget_event(event as StateEvent) =\r\n    print(\"FORGET EVENT\", event.Id, event.owner, event.name, event.state, event.datetime)\r\n    graph.Forget(event.Id)\r\nend\r\n\r\n### BALANCING ###\r\n\r\nlet getOwnerAccessPoint(obj as BaseObject) =\r\n    from graph.Events of type StateEvent where owner is string(obj.Id) select access_point try first\r\n\r\nlet getRandomAccessPoint(obj as BaseObject) = \r\n    let access_points = from graph.Values of type AccessPoint \r\n                        where user isnt null \r\n                        where obj.zonus in (zonus_list ?? empty)\r\n                        select string(Id) to array\r\n    if access_points isnt empty then access_points[random.Next(access_points.Length)]\r\n\r\nlet getZonusAccessPoint(obj as BaseObject) = (graph[obj.zonus] as Zonus)?.access_point   \r\n \r\nlet getAccessPoint(obj as BaseObject) = getOwnerAccessPoint(obj) ??? getRandomAccessPoint(obj) ??? getZonusAccessPoint(obj)\r\n\r\n### SEND ###\r\n\r\nlet send_event(obj as BaseObject) = \r\n    print(\"TRY SENDING EVENT BECAUSE OF\", obj.Id, obj, \"state = \", obj[\"state\"].Text)\r\n    \r\n    forget_old_events()\r\n        \r\n    let event          = StateEventImpl()\r\n    event.owner        = string(obj.Id)\r\n    event.server       = string(obj.ServerId)\r\n    event.position     = obj.position\r\n    event.state        = obj[\"state\"].Text?.Replace?('_', '.')\r\n    event.datetime     = obj[\"state\"].DateTime\r\n    event.version      = obj.Version\r\n    event.itemtype     = obj.GetType().Name\r\n    event.name         = obj.name\r\n    event.alarm_info   = (obj as Zone)?.alarm_info    \r\n    event.access_point = getAccessPoint(obj)\r\n    event.event_group  = event_groups.GetOrAdd(obj, DateTime.UtcNow.ToString(\"yyyy-MM-ddTHH:mm:ss.fffffff\")) \r\n    \r\n    if event isnt already_remembered then\r\n        print(\"SEND EVENT\", event.Id, event.owner, event.name, event.state, event.datetime)\r\n        create(event)\r\n        remember_event(event)\r\n        event.CommitChanges()\r\n    end\r\nend        \r\n\r\nlet try_send_event(obj as BaseObject) = \r\n    let state = obj[\"state\"].Text \r\n    let found = false  \r\n    from settings.state_event_reasons where Key in obj.Types \r\n    select many (from Value where state like it) take 1 do \r\n        found = true\r\n        send_event(obj)\r\n    now\r\n    let dummy = null as string\r\n    if not found then event_groups.TryRemove(obj, dummy)\r\nend\r\n\r\nlet update_event_param(owner as Item, param as Param) = \r\n    forget_old_events()\r\n    let id = string(owner.Id)\r\n    from graph.Events of type StateEvent where it.owner is id do\r\n        it[param.Name].Value = param.Value \r\n        update(it)\r\n    now\r\nend\r\n\r\n### SIREN ###\r\n\r\nlet try_play_siren(obj as BaseObject) = \r\n    let state = obj[\"state\"].Text \r\n    from settings.play_siren.reasons where Key in obj.Types \r\n    select many (from Value where state like Key select Value) take 1 do \r\n        let fileId = it\r\n        from settings.play_siren.paths \r\n        select many graph.Find(obj, it) of type SirenBase do\r\n            it.fileId  = fileId\r\n            it.command = \"start\"\r\n        now\r\n    now\r\nend\r\n\r\n### UPDATES ###\r\n\r\nlet on_update(source as AbstractObject, changes as string[]) = \r\n    if source is StreetLighting lamp then\r\n        if \"command\" in changes then\r\n            on_StreetLighting_command(lamp)\r\n\r\n    if source is StilSoftRelay relay then\r\n        if \"state\" in changes then\r\n            on_StilSoftRelay_state(relay)\r\n        \r\n    if source is BaseObject obj then\r\n        if \"state\" in changes then\r\n            try_send_event(obj) \r\n            try_play_siren(obj)\r\n            \r\n    if source is StateEvent event then\r\n    if (\"event_status\" in changes) and (event.event_status in forget_event_statuses) then\r\n            forget_event(event)\r\n            \r\n    if source is Zone zone then\r\n        if (\"alarm_info\" in changes) and (\"state\" isnt in changes) then\r\n            update_event_param(zone, zone[\"alarm_info\"])\r\n        \r\n                \r\n    void()\r\nend\r\nthis.RunOnUpdated(on_update)","datetime":1725365458.898,"status":null,"version":0},"name":{"value":"Misc Kerch","datetime":1699001378.0,"status":null,"version":0}},"entity":"item","operation":"create"}]