opensignalbox-interface 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- opensignalbox/interface/__init__.py +42 -0
- opensignalbox/interface/adapters/__init__.py +3 -0
- opensignalbox/interface/adapters/base.py +148 -0
- opensignalbox/interface/adapters/controller.py +286 -0
- opensignalbox/interface/adapters/modbus/__init__.py +3 -0
- opensignalbox/interface/adapters/modbus/adapter.py +485 -0
- opensignalbox/interface/assets/favicon.ico +0 -0
- opensignalbox/interface/interfaces/__init__.py +3 -0
- opensignalbox/interface/interfaces/base.py +170 -0
- opensignalbox/interface/interfaces/basicio/__init__.py +3 -0
- opensignalbox/interface/interfaces/basicio/connections.py +239 -0
- opensignalbox/interface/interfaces/basicio/handler.py +1096 -0
- opensignalbox/interface/interfaces/basicio/models.py +165 -0
- opensignalbox/interface/interfaces/bell/__init__.py +3 -0
- opensignalbox/interface/interfaces/bell/connections.py +279 -0
- opensignalbox/interface/interfaces/bell/handler.py +802 -0
- opensignalbox/interface/interfaces/bell/models.py +103 -0
- opensignalbox/interface/interfaces/controller.py +467 -0
- opensignalbox/interface/main.py +187 -0
- opensignalbox/interface/models.py +155 -0
- opensignalbox/interface/routes/__init__.py +4 -0
- opensignalbox/interface/routes/adapters.py +119 -0
- opensignalbox/interface/routes/interfaces.py +104 -0
- opensignalbox/interface/routes.py +204 -0
- opensignalbox/interface/utils.py +29 -0
- opensignalbox/interface/version.py +5 -0
- opensignalbox/interface/web_ui/.gitignore +24 -0
- opensignalbox/interface/web_ui/README.md +5 -0
- opensignalbox/interface/web_ui/WEB_UI_REFACTORING.md +136 -0
- opensignalbox/interface/web_ui/components.json +17 -0
- opensignalbox/interface/web_ui/dist/assets/index-DhsEc6uo.css +2020 -0
- opensignalbox/interface/web_ui/dist/assets/index-t__5JZjf.js +39056 -0
- opensignalbox/interface/web_ui/dist/assets/index-t__5JZjf.js.map +1 -0
- opensignalbox/interface/web_ui/dist/index.html +14 -0
- opensignalbox/interface/web_ui/index.html +13 -0
- opensignalbox/interface/web_ui/package-lock.json +3710 -0
- opensignalbox/interface/web_ui/package.json +39 -0
- opensignalbox/interface/web_ui/src/App.vue +32 -0
- opensignalbox/interface/web_ui/src/assets/favicon.ico +0 -0
- opensignalbox/interface/web_ui/src/assets/index.css +83 -0
- opensignalbox/interface/web_ui/src/components/SharedVariablePicker.vue +112 -0
- opensignalbox/interface/web_ui/src/components/adapters/AdapterList.vue +183 -0
- opensignalbox/interface/web_ui/src/components/adapters/types.ts +35 -0
- opensignalbox/interface/web_ui/src/components/interfaces/InterfaceList.vue +200 -0
- opensignalbox/interface/web_ui/src/components/ui/badge/Badge.vue +16 -0
- opensignalbox/interface/web_ui/src/components/ui/badge/index.ts +25 -0
- opensignalbox/interface/web_ui/src/components/ui/breadcrumb/Breadcrumb.vue +13 -0
- opensignalbox/interface/web_ui/src/components/ui/breadcrumb/BreadcrumbEllipsis.vue +22 -0
- opensignalbox/interface/web_ui/src/components/ui/breadcrumb/BreadcrumbItem.vue +16 -0
- opensignalbox/interface/web_ui/src/components/ui/breadcrumb/BreadcrumbLink.vue +19 -0
- opensignalbox/interface/web_ui/src/components/ui/breadcrumb/BreadcrumbList.vue +16 -0
- opensignalbox/interface/web_ui/src/components/ui/breadcrumb/BreadcrumbPage.vue +19 -0
- opensignalbox/interface/web_ui/src/components/ui/breadcrumb/BreadcrumbSeparator.vue +21 -0
- opensignalbox/interface/web_ui/src/components/ui/breadcrumb/index.ts +7 -0
- opensignalbox/interface/web_ui/src/components/ui/button/Button.vue +26 -0
- opensignalbox/interface/web_ui/src/components/ui/button/index.ts +35 -0
- opensignalbox/interface/web_ui/src/components/ui/card/Card.vue +21 -0
- opensignalbox/interface/web_ui/src/components/ui/card/CardContent.vue +14 -0
- opensignalbox/interface/web_ui/src/components/ui/card/CardDescription.vue +14 -0
- opensignalbox/interface/web_ui/src/components/ui/card/CardFooter.vue +14 -0
- opensignalbox/interface/web_ui/src/components/ui/card/CardHeader.vue +14 -0
- opensignalbox/interface/web_ui/src/components/ui/card/CardTitle.vue +18 -0
- opensignalbox/interface/web_ui/src/components/ui/card/index.ts +6 -0
- opensignalbox/interface/web_ui/src/components/ui/checkbox/Checkbox.vue +33 -0
- opensignalbox/interface/web_ui/src/components/ui/checkbox/index.ts +1 -0
- opensignalbox/interface/web_ui/src/components/ui/collapsible/Collapsible.vue +15 -0
- opensignalbox/interface/web_ui/src/components/ui/collapsible/CollapsibleContent.vue +11 -0
- opensignalbox/interface/web_ui/src/components/ui/collapsible/CollapsibleTrigger.vue +11 -0
- opensignalbox/interface/web_ui/src/components/ui/collapsible/index.ts +3 -0
- opensignalbox/interface/web_ui/src/components/ui/command/Command.vue +30 -0
- opensignalbox/interface/web_ui/src/components/ui/command/CommandDialog.vue +21 -0
- opensignalbox/interface/web_ui/src/components/ui/command/CommandEmpty.vue +20 -0
- opensignalbox/interface/web_ui/src/components/ui/command/CommandGroup.vue +29 -0
- opensignalbox/interface/web_ui/src/components/ui/command/CommandInput.vue +33 -0
- opensignalbox/interface/web_ui/src/components/ui/command/CommandItem.vue +26 -0
- opensignalbox/interface/web_ui/src/components/ui/command/CommandList.vue +27 -0
- opensignalbox/interface/web_ui/src/components/ui/command/CommandSeparator.vue +23 -0
- opensignalbox/interface/web_ui/src/components/ui/command/CommandShortcut.vue +14 -0
- opensignalbox/interface/web_ui/src/components/ui/command/index.ts +9 -0
- opensignalbox/interface/web_ui/src/components/ui/dialog/Dialog.vue +14 -0
- opensignalbox/interface/web_ui/src/components/ui/dialog/DialogClose.vue +11 -0
- opensignalbox/interface/web_ui/src/components/ui/dialog/DialogContent.vue +50 -0
- opensignalbox/interface/web_ui/src/components/ui/dialog/DialogDescription.vue +24 -0
- opensignalbox/interface/web_ui/src/components/ui/dialog/DialogFooter.vue +19 -0
- opensignalbox/interface/web_ui/src/components/ui/dialog/DialogHeader.vue +16 -0
- opensignalbox/interface/web_ui/src/components/ui/dialog/DialogScrollContent.vue +59 -0
- opensignalbox/interface/web_ui/src/components/ui/dialog/DialogTitle.vue +29 -0
- opensignalbox/interface/web_ui/src/components/ui/dialog/DialogTrigger.vue +11 -0
- opensignalbox/interface/web_ui/src/components/ui/dialog/index.ts +9 -0
- opensignalbox/interface/web_ui/src/components/ui/dropdown-menu/DropdownMenu.vue +14 -0
- opensignalbox/interface/web_ui/src/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +40 -0
- opensignalbox/interface/web_ui/src/components/ui/dropdown-menu/DropdownMenuContent.vue +38 -0
- opensignalbox/interface/web_ui/src/components/ui/dropdown-menu/DropdownMenuGroup.vue +11 -0
- opensignalbox/interface/web_ui/src/components/ui/dropdown-menu/DropdownMenuItem.vue +28 -0
- opensignalbox/interface/web_ui/src/components/ui/dropdown-menu/DropdownMenuLabel.vue +24 -0
- opensignalbox/interface/web_ui/src/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue +19 -0
- opensignalbox/interface/web_ui/src/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +41 -0
- opensignalbox/interface/web_ui/src/components/ui/dropdown-menu/DropdownMenuSeparator.vue +22 -0
- opensignalbox/interface/web_ui/src/components/ui/dropdown-menu/DropdownMenuShortcut.vue +14 -0
- opensignalbox/interface/web_ui/src/components/ui/dropdown-menu/DropdownMenuSub.vue +19 -0
- opensignalbox/interface/web_ui/src/components/ui/dropdown-menu/DropdownMenuSubContent.vue +30 -0
- opensignalbox/interface/web_ui/src/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +33 -0
- opensignalbox/interface/web_ui/src/components/ui/dropdown-menu/DropdownMenuTrigger.vue +13 -0
- opensignalbox/interface/web_ui/src/components/ui/dropdown-menu/index.ts +16 -0
- opensignalbox/interface/web_ui/src/components/ui/form/FormControl.vue +16 -0
- opensignalbox/interface/web_ui/src/components/ui/form/FormDescription.vue +20 -0
- opensignalbox/interface/web_ui/src/components/ui/form/FormItem.vue +19 -0
- opensignalbox/interface/web_ui/src/components/ui/form/FormLabel.vue +23 -0
- opensignalbox/interface/web_ui/src/components/ui/form/FormMessage.vue +16 -0
- opensignalbox/interface/web_ui/src/components/ui/form/index.ts +7 -0
- opensignalbox/interface/web_ui/src/components/ui/form/injectionKeys.ts +4 -0
- opensignalbox/interface/web_ui/src/components/ui/form/useFormField.ts +30 -0
- opensignalbox/interface/web_ui/src/components/ui/input/Input.vue +24 -0
- opensignalbox/interface/web_ui/src/components/ui/input/index.ts +1 -0
- opensignalbox/interface/web_ui/src/components/ui/label/Label.vue +27 -0
- opensignalbox/interface/web_ui/src/components/ui/label/index.ts +1 -0
- opensignalbox/interface/web_ui/src/components/ui/number-field/NumberField.vue +23 -0
- opensignalbox/interface/web_ui/src/components/ui/number-field/NumberFieldContent.vue +14 -0
- opensignalbox/interface/web_ui/src/components/ui/number-field/NumberFieldDecrement.vue +25 -0
- opensignalbox/interface/web_ui/src/components/ui/number-field/NumberFieldIncrement.vue +25 -0
- opensignalbox/interface/web_ui/src/components/ui/number-field/NumberFieldInput.vue +16 -0
- opensignalbox/interface/web_ui/src/components/ui/number-field/index.ts +5 -0
- opensignalbox/interface/web_ui/src/components/ui/popover/Popover.vue +15 -0
- opensignalbox/interface/web_ui/src/components/ui/popover/PopoverContent.vue +48 -0
- opensignalbox/interface/web_ui/src/components/ui/popover/PopoverTrigger.vue +11 -0
- opensignalbox/interface/web_ui/src/components/ui/popover/index.ts +3 -0
- opensignalbox/interface/web_ui/src/components/ui/radio-group/RadioGroup.vue +25 -0
- opensignalbox/interface/web_ui/src/components/ui/radio-group/RadioGroupItem.vue +39 -0
- opensignalbox/interface/web_ui/src/components/ui/radio-group/index.ts +2 -0
- opensignalbox/interface/web_ui/src/components/ui/scroll-area/ScrollArea.vue +29 -0
- opensignalbox/interface/web_ui/src/components/ui/scroll-area/ScrollBar.vue +30 -0
- opensignalbox/interface/web_ui/src/components/ui/scroll-area/index.ts +2 -0
- opensignalbox/interface/web_ui/src/components/ui/select/Select.vue +15 -0
- opensignalbox/interface/web_ui/src/components/ui/select/SelectContent.vue +53 -0
- opensignalbox/interface/web_ui/src/components/ui/select/SelectGroup.vue +19 -0
- opensignalbox/interface/web_ui/src/components/ui/select/SelectItem.vue +44 -0
- opensignalbox/interface/web_ui/src/components/ui/select/SelectItemText.vue +11 -0
- opensignalbox/interface/web_ui/src/components/ui/select/SelectLabel.vue +13 -0
- opensignalbox/interface/web_ui/src/components/ui/select/SelectScrollDownButton.vue +24 -0
- opensignalbox/interface/web_ui/src/components/ui/select/SelectScrollUpButton.vue +24 -0
- opensignalbox/interface/web_ui/src/components/ui/select/SelectSeparator.vue +17 -0
- opensignalbox/interface/web_ui/src/components/ui/select/SelectTrigger.vue +31 -0
- opensignalbox/interface/web_ui/src/components/ui/select/SelectValue.vue +11 -0
- opensignalbox/interface/web_ui/src/components/ui/select/index.ts +11 -0
- opensignalbox/interface/web_ui/src/components/ui/separator/Separator.vue +35 -0
- opensignalbox/interface/web_ui/src/components/ui/separator/index.ts +1 -0
- opensignalbox/interface/web_ui/src/components/ui/sheet/Sheet.vue +14 -0
- opensignalbox/interface/web_ui/src/components/ui/sheet/SheetClose.vue +11 -0
- opensignalbox/interface/web_ui/src/components/ui/sheet/SheetContent.vue +56 -0
- opensignalbox/interface/web_ui/src/components/ui/sheet/SheetDescription.vue +22 -0
- opensignalbox/interface/web_ui/src/components/ui/sheet/SheetFooter.vue +19 -0
- opensignalbox/interface/web_ui/src/components/ui/sheet/SheetHeader.vue +16 -0
- opensignalbox/interface/web_ui/src/components/ui/sheet/SheetTitle.vue +22 -0
- opensignalbox/interface/web_ui/src/components/ui/sheet/SheetTrigger.vue +11 -0
- opensignalbox/interface/web_ui/src/components/ui/sheet/index.ts +31 -0
- opensignalbox/interface/web_ui/src/components/ui/table/Table.vue +16 -0
- opensignalbox/interface/web_ui/src/components/ui/table/TableBody.vue +14 -0
- opensignalbox/interface/web_ui/src/components/ui/table/TableCaption.vue +14 -0
- opensignalbox/interface/web_ui/src/components/ui/table/TableCell.vue +21 -0
- opensignalbox/interface/web_ui/src/components/ui/table/TableEmpty.vue +37 -0
- opensignalbox/interface/web_ui/src/components/ui/table/TableFooter.vue +14 -0
- opensignalbox/interface/web_ui/src/components/ui/table/TableHead.vue +14 -0
- opensignalbox/interface/web_ui/src/components/ui/table/TableHeader.vue +14 -0
- opensignalbox/interface/web_ui/src/components/ui/table/TableRow.vue +14 -0
- opensignalbox/interface/web_ui/src/components/ui/table/index.ts +9 -0
- opensignalbox/interface/web_ui/src/components/ui/tabs/Tabs.vue +15 -0
- opensignalbox/interface/web_ui/src/components/ui/tabs/TabsContent.vue +22 -0
- opensignalbox/interface/web_ui/src/components/ui/tabs/TabsList.vue +25 -0
- opensignalbox/interface/web_ui/src/components/ui/tabs/TabsTrigger.vue +29 -0
- opensignalbox/interface/web_ui/src/components/ui/tabs/index.ts +4 -0
- opensignalbox/interface/web_ui/src/components/ui/tags-input/TagsInput.vue +22 -0
- opensignalbox/interface/web_ui/src/components/ui/tags-input/TagsInputInput.vue +19 -0
- opensignalbox/interface/web_ui/src/components/ui/tags-input/TagsInputItem.vue +22 -0
- opensignalbox/interface/web_ui/src/components/ui/tags-input/TagsInputItemDelete.vue +24 -0
- opensignalbox/interface/web_ui/src/components/ui/tags-input/TagsInputItemText.vue +19 -0
- opensignalbox/interface/web_ui/src/components/ui/tags-input/index.ts +5 -0
- opensignalbox/interface/web_ui/src/components/ui/textarea/Textarea.vue +24 -0
- opensignalbox/interface/web_ui/src/components/ui/textarea/index.ts +1 -0
- opensignalbox/interface/web_ui/src/components/ui/tooltip/Tooltip.vue +14 -0
- opensignalbox/interface/web_ui/src/components/ui/tooltip/TooltipContent.vue +31 -0
- opensignalbox/interface/web_ui/src/components/ui/tooltip/TooltipProvider.vue +11 -0
- opensignalbox/interface/web_ui/src/components/ui/tooltip/TooltipTrigger.vue +11 -0
- opensignalbox/interface/web_ui/src/components/ui/tooltip/index.ts +4 -0
- opensignalbox/interface/web_ui/src/libs/utils.ts +6 -0
- opensignalbox/interface/web_ui/src/main.ts +141 -0
- opensignalbox/interface/web_ui/src/views/Overview.vue +11 -0
- opensignalbox/interface/web_ui/src/views/Settings.vue +6 -0
- opensignalbox/interface/web_ui/src/views/SleafordEast.vue +205 -0
- opensignalbox/interface/web_ui/src/views/adapters/modbus/ModbusAdapterEdit.vue +343 -0
- opensignalbox/interface/web_ui/src/views/adapters/modbus/ModbusAdapterView.vue +270 -0
- opensignalbox/interface/web_ui/src/views/interfaces/InterfaceCreate.vue +0 -0
- opensignalbox/interface/web_ui/src/views/interfaces/basicio/BasicIOInterfaceEdit.vue +795 -0
- opensignalbox/interface/web_ui/src/views/interfaces/basicio/BasicIOInterfaceView.vue +648 -0
- opensignalbox/interface/web_ui/src/views/interfaces/bell/BellInterfaceEdit.vue +790 -0
- opensignalbox/interface/web_ui/src/views/interfaces/bell/BellInterfaceView.vue +437 -0
- opensignalbox/interface/web_ui/src/vite-env.d.ts +1 -0
- opensignalbox/interface/web_ui/tailwind.config.js +94 -0
- opensignalbox/interface/web_ui/tsconfig.app.json +27 -0
- opensignalbox/interface/web_ui/tsconfig.json +30 -0
- opensignalbox/interface/web_ui/tsconfig.node.json +12 -0
- opensignalbox/interface/web_ui/tsconfig.tsbuildinfo +1 -0
- opensignalbox/interface/web_ui/vite.config.d.ts +2 -0
- opensignalbox/interface/web_ui/vite.config.js +60 -0
- opensignalbox/interface/web_ui/vite.config.ts +62 -0
- opensignalbox_interface-0.1.0.dist-info/METADATA +49 -0
- opensignalbox_interface-0.1.0.dist-info/RECORD +209 -0
- opensignalbox_interface-0.1.0.dist-info/WHEEL +4 -0
- opensignalbox_interface-0.1.0.dist-info/entry_points.txt +2 -0
- opensignalbox_interface-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Basic IO interface models.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from collections import namedtuple
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from typing import Dict, List, Literal, Optional
|
|
8
|
+
|
|
9
|
+
from opensignalbox.common.models import CamelModel
|
|
10
|
+
from opensignalbox.common.variables import SharedVariable
|
|
11
|
+
from opensignalbox.interface.models import (
|
|
12
|
+
Interface,
|
|
13
|
+
InterfaceBase,
|
|
14
|
+
InterfaceTypeEnum,
|
|
15
|
+
InterfaceUpdate,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
__MAX_CHANNEL_COUNT__ = 128
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class BasicIOConnectionType(str, Enum):
|
|
22
|
+
"""Connection types for BasicIO interfaces."""
|
|
23
|
+
|
|
24
|
+
MODBUS_ADAPTER = "ModbusAdapter"
|
|
25
|
+
DINGTIAN_MODBUS_ADAPTER = "DingtianModbusAdapter" # Used at Lowdham
|
|
26
|
+
DIRECT_TCP = "DirectTCP"
|
|
27
|
+
TEST = "Test" # For testing purposes only
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ForceChannelTypeEnum(str, Enum):
|
|
31
|
+
"""Enum for force channel types."""
|
|
32
|
+
|
|
33
|
+
INPUT = "input"
|
|
34
|
+
OUTPUT = "output"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ForceStateEnum(str, Enum):
|
|
38
|
+
"""Enum for force states."""
|
|
39
|
+
|
|
40
|
+
ON = "on"
|
|
41
|
+
OFF = "off"
|
|
42
|
+
NONE = "none" # To remove forcing
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# Tuple for pairing interface name with channel number
|
|
46
|
+
IfaceChanTuple = namedtuple("IfaceChanTuple", ["interface", "channel"])
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class InputChannelBase(CamelModel):
|
|
50
|
+
"""Base model for input channels."""
|
|
51
|
+
|
|
52
|
+
system_name: str
|
|
53
|
+
user_name: str
|
|
54
|
+
description: str
|
|
55
|
+
tags: List[str]
|
|
56
|
+
default_value: bool
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class InputChannelCreate(InputChannelBase):
|
|
60
|
+
"""API model for creating input channels."""
|
|
61
|
+
|
|
62
|
+
channel: int
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class OutputChannelBase(CamelModel):
|
|
66
|
+
"""Base model for output channels."""
|
|
67
|
+
|
|
68
|
+
shared_variable_name: Optional[str] = None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class OutputChannelCreate(OutputChannelBase):
|
|
72
|
+
"""API model for creating output channels."""
|
|
73
|
+
|
|
74
|
+
channel: int
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class InputChannel(InputChannelBase):
|
|
78
|
+
"""Runtime model for input channels."""
|
|
79
|
+
|
|
80
|
+
hardware_value: bool = False
|
|
81
|
+
force_enable: bool = False
|
|
82
|
+
force_value: bool = False
|
|
83
|
+
variable_value: bool = False
|
|
84
|
+
shared_variable: Optional[SharedVariable] = None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class OutputChannel(OutputChannelBase):
|
|
88
|
+
"""Runtime model for output channels."""
|
|
89
|
+
|
|
90
|
+
variable_value: bool = False
|
|
91
|
+
force_enable: bool = False
|
|
92
|
+
force_value: bool = False
|
|
93
|
+
hardware_value: bool = False
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class BasicIOInterfaceFields(CamelModel):
|
|
97
|
+
"""Mixin with BasicIO-specific fields."""
|
|
98
|
+
|
|
99
|
+
connection_type: BasicIOConnectionType = BasicIOConnectionType.MODBUS_ADAPTER
|
|
100
|
+
input_start_address: int = 0
|
|
101
|
+
input_bits: int = 0
|
|
102
|
+
input_byte_swap: bool = False
|
|
103
|
+
input_word_swap: bool = False
|
|
104
|
+
output_start_address: int = 0
|
|
105
|
+
output_bits: int = 0
|
|
106
|
+
output_byte_swap: bool = False
|
|
107
|
+
output_word_swap: bool = False
|
|
108
|
+
|
|
109
|
+
# Fields for modbus connection
|
|
110
|
+
modbus_address: Optional[int] = None
|
|
111
|
+
|
|
112
|
+
# Fields for TCP connection
|
|
113
|
+
tcp_host: Optional[str] = None
|
|
114
|
+
tcp_port: Optional[int] = None
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class BasicIOInterfaceBase(Interface, BasicIOInterfaceFields):
|
|
118
|
+
"""Base model with BasicIO-specific fields."""
|
|
119
|
+
|
|
120
|
+
pass
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class BasicIOInterface(BasicIOInterfaceBase):
|
|
124
|
+
"""Runtime model for BasicIO interfaces."""
|
|
125
|
+
|
|
126
|
+
input_channels: Dict[int, InputChannel] = {}
|
|
127
|
+
output_channels: Dict[int, OutputChannel] = {}
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class BasicIOInterfaceCreate(InterfaceBase, BasicIOInterfaceFields):
|
|
131
|
+
"""API model for creating BasicIO interfaces."""
|
|
132
|
+
|
|
133
|
+
interface_type: Literal[InterfaceTypeEnum.BASICIO] = InterfaceTypeEnum.BASICIO
|
|
134
|
+
input_map: List[InputChannelCreate] = []
|
|
135
|
+
output_map: List[OutputChannelCreate] = []
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class BasicIOInterfacePublic(BasicIOInterfaceBase):
|
|
139
|
+
"""API model for BasicIO interface responses."""
|
|
140
|
+
|
|
141
|
+
input_map: List[InputChannelCreate] = []
|
|
142
|
+
output_map: List[OutputChannelCreate] = []
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class BasicIOInterfaceUpdate(InterfaceUpdate):
|
|
146
|
+
"""API model for updating BasicIO interfaces."""
|
|
147
|
+
|
|
148
|
+
input_start_address: Optional[int] = None
|
|
149
|
+
input_bits: Optional[int] = None
|
|
150
|
+
input_byte_swap: Optional[bool] = None
|
|
151
|
+
input_word_swap: Optional[bool] = None
|
|
152
|
+
output_start_address: Optional[int] = None
|
|
153
|
+
output_bits: Optional[int] = None
|
|
154
|
+
output_byte_swap: Optional[bool] = None
|
|
155
|
+
output_word_swap: Optional[bool] = None
|
|
156
|
+
input_map: Optional[List[InputChannelCreate]] = None
|
|
157
|
+
output_map: Optional[List[OutputChannelCreate]] = None
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class BasicIOForceMessage(CamelModel):
|
|
161
|
+
"""Message for setting force state on BasicIO channels."""
|
|
162
|
+
|
|
163
|
+
iotype: ForceChannelTypeEnum
|
|
164
|
+
channel: int
|
|
165
|
+
state: ForceStateEnum
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import logging
|
|
3
|
+
import socket
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
|
|
6
|
+
from opensignalbox.interface.adapters.base import BaseAdapter
|
|
7
|
+
from opensignalbox.interface.interfaces.bell.models import BellInterface
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def modbus_exception_handler(func):
|
|
13
|
+
@functools.wraps(func)
|
|
14
|
+
def wrapper(cls, interface, *args, **kwargs):
|
|
15
|
+
try:
|
|
16
|
+
return func(cls, interface, *args, **kwargs)
|
|
17
|
+
except (BrokenPipeError, ConnectionResetError, OSError, socket.error) as e:
|
|
18
|
+
logger.error(
|
|
19
|
+
f"Modbus TCP error for '{interface.system_name}' channel {kwargs.get('channel', 'n/a')}: {e}"
|
|
20
|
+
)
|
|
21
|
+
interface.error = f"TCP error: {e}"
|
|
22
|
+
BellModbusConnection.disconnect(interface, kwargs.get("adapter"))
|
|
23
|
+
BellModbusConnection.connect(interface, kwargs.get("adapter"))
|
|
24
|
+
return (
|
|
25
|
+
bytearray(b"")
|
|
26
|
+
if func.__name__.startswith("receive")
|
|
27
|
+
or func.__name__.startswith("get")
|
|
28
|
+
else None
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
return wrapper
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class BellConnection(ABC):
|
|
35
|
+
"""Base abstract class for all bell connections."""
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
@abstractmethod
|
|
39
|
+
def connect(
|
|
40
|
+
cls, interface: BellInterface, adapter: BaseAdapter | None = None
|
|
41
|
+
) -> bool:
|
|
42
|
+
"""Connect to the bell interface."""
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
@abstractmethod
|
|
47
|
+
def disconnect(
|
|
48
|
+
cls, interface: BellInterface, adapter: BaseAdapter | None = None
|
|
49
|
+
) -> bool:
|
|
50
|
+
"""Disconnect from the bell interface."""
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
@classmethod
|
|
54
|
+
@abstractmethod
|
|
55
|
+
def send_code(
|
|
56
|
+
cls,
|
|
57
|
+
interface: BellInterface,
|
|
58
|
+
channel: int,
|
|
59
|
+
data: bytearray,
|
|
60
|
+
adapter: BaseAdapter | None = None,
|
|
61
|
+
) -> None:
|
|
62
|
+
"""Send data through the bell interface."""
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
@classmethod
|
|
66
|
+
@abstractmethod
|
|
67
|
+
def receive_code(
|
|
68
|
+
cls, interface: BellInterface, channel: int, adapter: BaseAdapter | None = None
|
|
69
|
+
) -> bytearray:
|
|
70
|
+
"""Receive data from the bell interface."""
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
@classmethod
|
|
74
|
+
@abstractmethod
|
|
75
|
+
def receive_all_codes(
|
|
76
|
+
cls, interface: BellInterface, adapter: BaseAdapter | None = None
|
|
77
|
+
) -> bytearray:
|
|
78
|
+
"""Receive data from the bell interface."""
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class BellTestConnection(BellConnection):
|
|
83
|
+
"""Test implementation of the BellConnection for unit testing."""
|
|
84
|
+
|
|
85
|
+
in_1_count: int = 1
|
|
86
|
+
out_1_count: int = 0
|
|
87
|
+
in_2_count: int = 1
|
|
88
|
+
out_2_count: int = 0
|
|
89
|
+
in_3_count: int = 1
|
|
90
|
+
out_3_count: int = 0
|
|
91
|
+
in_4_count: int = 1
|
|
92
|
+
out_4_count: int = 0
|
|
93
|
+
|
|
94
|
+
@classmethod
|
|
95
|
+
def connect(
|
|
96
|
+
cls, interface: BellInterface, adapter: BaseAdapter | None = None
|
|
97
|
+
) -> bool:
|
|
98
|
+
"""Simulate connecting to the interface."""
|
|
99
|
+
logger.debug(
|
|
100
|
+
f"Connecting to interface {interface.system_name} with test connection."
|
|
101
|
+
)
|
|
102
|
+
interface.is_connected = True
|
|
103
|
+
return True
|
|
104
|
+
|
|
105
|
+
@classmethod
|
|
106
|
+
def disconnect(
|
|
107
|
+
cls, interface: BellInterface, adapter: BaseAdapter | None = None
|
|
108
|
+
) -> bool:
|
|
109
|
+
"""Simulate disconnecting from the interface."""
|
|
110
|
+
logger.debug(
|
|
111
|
+
f"Disconnecting from interface {interface.system_name} with test connection."
|
|
112
|
+
)
|
|
113
|
+
interface.is_connected = False
|
|
114
|
+
return True
|
|
115
|
+
|
|
116
|
+
@classmethod
|
|
117
|
+
def send_code(
|
|
118
|
+
cls,
|
|
119
|
+
interface: BellInterface,
|
|
120
|
+
channel: int,
|
|
121
|
+
data: bytearray,
|
|
122
|
+
adapter: BaseAdapter | None = None,
|
|
123
|
+
) -> None:
|
|
124
|
+
"""Simulate sending data."""
|
|
125
|
+
# logger.debug(f"Sending data to interface {interface.system_name} on channel {channel} with test connection. Data: {data.hex()}")
|
|
126
|
+
return
|
|
127
|
+
|
|
128
|
+
@classmethod
|
|
129
|
+
def receive_code(
|
|
130
|
+
cls, interface: BellInterface, channel: int, adapter: BaseAdapter | None = None
|
|
131
|
+
) -> bytearray:
|
|
132
|
+
"""Simulate receiving data."""
|
|
133
|
+
response = bytearray()
|
|
134
|
+
count = interface.channels[channel].out_count
|
|
135
|
+
code = interface.channels[channel].out_code
|
|
136
|
+
|
|
137
|
+
response.append((count >> 8) & 0xFF)
|
|
138
|
+
response.append(count & 0xFF)
|
|
139
|
+
|
|
140
|
+
for i in range(1, 8):
|
|
141
|
+
if i - 1 < len(code.split(",")):
|
|
142
|
+
try:
|
|
143
|
+
response += (int(code.split(",")[i - 1])).to_bytes(2)
|
|
144
|
+
except ValueError:
|
|
145
|
+
response += (0).to_bytes(2)
|
|
146
|
+
else:
|
|
147
|
+
response += (0).to_bytes(2)
|
|
148
|
+
|
|
149
|
+
# logger.debug(f"Receiving data from interface {interface.system_name} on channel {channel} with test connection. Data: {response.hex()}")
|
|
150
|
+
# Simulate receiving data
|
|
151
|
+
return response
|
|
152
|
+
|
|
153
|
+
@classmethod
|
|
154
|
+
def receive_all_codes(
|
|
155
|
+
cls, interface: BellInterface, adapter: BaseAdapter | None = None
|
|
156
|
+
) -> bytearray:
|
|
157
|
+
"""Simulate receiving data."""
|
|
158
|
+
response = bytearray()
|
|
159
|
+
return response
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class BellModbusConnection(BellConnection):
|
|
163
|
+
"""Modbus implementation of the BellConnection."""
|
|
164
|
+
|
|
165
|
+
in_start_addresses = {
|
|
166
|
+
1: 41000,
|
|
167
|
+
2: 42000,
|
|
168
|
+
3: 43000,
|
|
169
|
+
4: 44000,
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
in_fast_start_address = 45000
|
|
173
|
+
|
|
174
|
+
out_start_addresses = {
|
|
175
|
+
1: 41100,
|
|
176
|
+
2: 42100,
|
|
177
|
+
3: 43100,
|
|
178
|
+
4: 44100,
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
code_length: int = 8
|
|
182
|
+
|
|
183
|
+
@classmethod
|
|
184
|
+
def connect(
|
|
185
|
+
cls, interface: BellInterface, adapter: BaseAdapter | None = None
|
|
186
|
+
) -> bool:
|
|
187
|
+
"""Connect to the Modbus interface."""
|
|
188
|
+
logger.debug(f"Connecting to Bell Modbus interface {interface.system_name}.")
|
|
189
|
+
if adapter is not None:
|
|
190
|
+
if not adapter.is_connected:
|
|
191
|
+
adapter.connect()
|
|
192
|
+
if adapter.is_connected:
|
|
193
|
+
interface.is_connected = True
|
|
194
|
+
return True
|
|
195
|
+
else:
|
|
196
|
+
interface.is_connected = True
|
|
197
|
+
return True
|
|
198
|
+
return False
|
|
199
|
+
|
|
200
|
+
@classmethod
|
|
201
|
+
def disconnect(
|
|
202
|
+
cls, interface: BellInterface, adapter: BaseAdapter | None = None
|
|
203
|
+
) -> bool:
|
|
204
|
+
"""Disconnect from the Modbus interface."""
|
|
205
|
+
logger.debug(
|
|
206
|
+
f"Disconnecting from Bell Modbus interface {interface.system_name}."
|
|
207
|
+
)
|
|
208
|
+
interface.is_connected = False
|
|
209
|
+
return True
|
|
210
|
+
|
|
211
|
+
@classmethod
|
|
212
|
+
@modbus_exception_handler
|
|
213
|
+
def send_code(
|
|
214
|
+
cls,
|
|
215
|
+
interface: BellInterface,
|
|
216
|
+
channel: int,
|
|
217
|
+
data: bytearray,
|
|
218
|
+
adapter: BaseAdapter | None = None,
|
|
219
|
+
) -> None:
|
|
220
|
+
"""Send data through the Modbus interface."""
|
|
221
|
+
if adapter is not None and interface.modbus_address is not None:
|
|
222
|
+
command = 16
|
|
223
|
+
cmd = interface.modbus_address.to_bytes()
|
|
224
|
+
cmd += command.to_bytes()
|
|
225
|
+
cmd += cls.out_start_addresses[channel].to_bytes(2)
|
|
226
|
+
cmd += cls.code_length.to_bytes(2)
|
|
227
|
+
cmd += len(data).to_bytes()
|
|
228
|
+
cmd += data
|
|
229
|
+
adapter.attempt_write(cmd)
|
|
230
|
+
adapter.attempt_read()
|
|
231
|
+
return
|
|
232
|
+
|
|
233
|
+
@classmethod
|
|
234
|
+
@modbus_exception_handler
|
|
235
|
+
def receive_code(
|
|
236
|
+
cls, interface: BellInterface, channel: int, adapter: BaseAdapter | None = None
|
|
237
|
+
) -> bytearray:
|
|
238
|
+
"""Receive data from the Modbus interface."""
|
|
239
|
+
if adapter is not None and interface.modbus_address is not None:
|
|
240
|
+
command = 3
|
|
241
|
+
cmd = interface.modbus_address.to_bytes()
|
|
242
|
+
cmd += command.to_bytes()
|
|
243
|
+
cmd += cls.in_start_addresses[channel].to_bytes(2)
|
|
244
|
+
cmd += cls.code_length.to_bytes(2)
|
|
245
|
+
adapter.attempt_write(cmd)
|
|
246
|
+
return adapter.attempt_read()
|
|
247
|
+
return bytearray(b"")
|
|
248
|
+
|
|
249
|
+
@classmethod
|
|
250
|
+
@modbus_exception_handler
|
|
251
|
+
def receive_all_codes(
|
|
252
|
+
cls, interface: BellInterface, adapter: BaseAdapter | None = None
|
|
253
|
+
) -> bytearray:
|
|
254
|
+
"""Receive data from the Modbus interface."""
|
|
255
|
+
if adapter is not None and interface.modbus_address is not None:
|
|
256
|
+
command = 3
|
|
257
|
+
cmd = interface.modbus_address.to_bytes()
|
|
258
|
+
cmd += command.to_bytes()
|
|
259
|
+
cmd += cls.in_fast_start_address.to_bytes(2)
|
|
260
|
+
cmd += (cls.code_length * 4 + 4).to_bytes(2)
|
|
261
|
+
adapter.attempt_write(cmd)
|
|
262
|
+
return adapter.attempt_read()
|
|
263
|
+
return bytearray(b"")
|
|
264
|
+
|
|
265
|
+
@classmethod
|
|
266
|
+
@modbus_exception_handler
|
|
267
|
+
def get_sent_code(
|
|
268
|
+
cls, interface: BellInterface, channel: int, adapter: BaseAdapter | None = None
|
|
269
|
+
) -> bytearray:
|
|
270
|
+
"""Get the last sent code for a channel."""
|
|
271
|
+
if adapter is not None and interface.modbus_address is not None:
|
|
272
|
+
command = 3
|
|
273
|
+
cmd = interface.modbus_address.to_bytes()
|
|
274
|
+
cmd += command.to_bytes()
|
|
275
|
+
cmd += cls.out_start_addresses[channel].to_bytes(2)
|
|
276
|
+
cmd += cls.code_length.to_bytes(2)
|
|
277
|
+
adapter.attempt_write(cmd)
|
|
278
|
+
return adapter.attempt_read()
|
|
279
|
+
return bytearray(b"")
|