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.
Files changed (209) hide show
  1. opensignalbox/interface/__init__.py +42 -0
  2. opensignalbox/interface/adapters/__init__.py +3 -0
  3. opensignalbox/interface/adapters/base.py +148 -0
  4. opensignalbox/interface/adapters/controller.py +286 -0
  5. opensignalbox/interface/adapters/modbus/__init__.py +3 -0
  6. opensignalbox/interface/adapters/modbus/adapter.py +485 -0
  7. opensignalbox/interface/assets/favicon.ico +0 -0
  8. opensignalbox/interface/interfaces/__init__.py +3 -0
  9. opensignalbox/interface/interfaces/base.py +170 -0
  10. opensignalbox/interface/interfaces/basicio/__init__.py +3 -0
  11. opensignalbox/interface/interfaces/basicio/connections.py +239 -0
  12. opensignalbox/interface/interfaces/basicio/handler.py +1096 -0
  13. opensignalbox/interface/interfaces/basicio/models.py +165 -0
  14. opensignalbox/interface/interfaces/bell/__init__.py +3 -0
  15. opensignalbox/interface/interfaces/bell/connections.py +279 -0
  16. opensignalbox/interface/interfaces/bell/handler.py +802 -0
  17. opensignalbox/interface/interfaces/bell/models.py +103 -0
  18. opensignalbox/interface/interfaces/controller.py +467 -0
  19. opensignalbox/interface/main.py +187 -0
  20. opensignalbox/interface/models.py +155 -0
  21. opensignalbox/interface/routes/__init__.py +4 -0
  22. opensignalbox/interface/routes/adapters.py +119 -0
  23. opensignalbox/interface/routes/interfaces.py +104 -0
  24. opensignalbox/interface/routes.py +204 -0
  25. opensignalbox/interface/utils.py +29 -0
  26. opensignalbox/interface/version.py +5 -0
  27. opensignalbox/interface/web_ui/.gitignore +24 -0
  28. opensignalbox/interface/web_ui/README.md +5 -0
  29. opensignalbox/interface/web_ui/WEB_UI_REFACTORING.md +136 -0
  30. opensignalbox/interface/web_ui/components.json +17 -0
  31. opensignalbox/interface/web_ui/dist/assets/index-DhsEc6uo.css +2020 -0
  32. opensignalbox/interface/web_ui/dist/assets/index-t__5JZjf.js +39056 -0
  33. opensignalbox/interface/web_ui/dist/assets/index-t__5JZjf.js.map +1 -0
  34. opensignalbox/interface/web_ui/dist/index.html +14 -0
  35. opensignalbox/interface/web_ui/index.html +13 -0
  36. opensignalbox/interface/web_ui/package-lock.json +3710 -0
  37. opensignalbox/interface/web_ui/package.json +39 -0
  38. opensignalbox/interface/web_ui/src/App.vue +32 -0
  39. opensignalbox/interface/web_ui/src/assets/favicon.ico +0 -0
  40. opensignalbox/interface/web_ui/src/assets/index.css +83 -0
  41. opensignalbox/interface/web_ui/src/components/SharedVariablePicker.vue +112 -0
  42. opensignalbox/interface/web_ui/src/components/adapters/AdapterList.vue +183 -0
  43. opensignalbox/interface/web_ui/src/components/adapters/types.ts +35 -0
  44. opensignalbox/interface/web_ui/src/components/interfaces/InterfaceList.vue +200 -0
  45. opensignalbox/interface/web_ui/src/components/ui/badge/Badge.vue +16 -0
  46. opensignalbox/interface/web_ui/src/components/ui/badge/index.ts +25 -0
  47. opensignalbox/interface/web_ui/src/components/ui/breadcrumb/Breadcrumb.vue +13 -0
  48. opensignalbox/interface/web_ui/src/components/ui/breadcrumb/BreadcrumbEllipsis.vue +22 -0
  49. opensignalbox/interface/web_ui/src/components/ui/breadcrumb/BreadcrumbItem.vue +16 -0
  50. opensignalbox/interface/web_ui/src/components/ui/breadcrumb/BreadcrumbLink.vue +19 -0
  51. opensignalbox/interface/web_ui/src/components/ui/breadcrumb/BreadcrumbList.vue +16 -0
  52. opensignalbox/interface/web_ui/src/components/ui/breadcrumb/BreadcrumbPage.vue +19 -0
  53. opensignalbox/interface/web_ui/src/components/ui/breadcrumb/BreadcrumbSeparator.vue +21 -0
  54. opensignalbox/interface/web_ui/src/components/ui/breadcrumb/index.ts +7 -0
  55. opensignalbox/interface/web_ui/src/components/ui/button/Button.vue +26 -0
  56. opensignalbox/interface/web_ui/src/components/ui/button/index.ts +35 -0
  57. opensignalbox/interface/web_ui/src/components/ui/card/Card.vue +21 -0
  58. opensignalbox/interface/web_ui/src/components/ui/card/CardContent.vue +14 -0
  59. opensignalbox/interface/web_ui/src/components/ui/card/CardDescription.vue +14 -0
  60. opensignalbox/interface/web_ui/src/components/ui/card/CardFooter.vue +14 -0
  61. opensignalbox/interface/web_ui/src/components/ui/card/CardHeader.vue +14 -0
  62. opensignalbox/interface/web_ui/src/components/ui/card/CardTitle.vue +18 -0
  63. opensignalbox/interface/web_ui/src/components/ui/card/index.ts +6 -0
  64. opensignalbox/interface/web_ui/src/components/ui/checkbox/Checkbox.vue +33 -0
  65. opensignalbox/interface/web_ui/src/components/ui/checkbox/index.ts +1 -0
  66. opensignalbox/interface/web_ui/src/components/ui/collapsible/Collapsible.vue +15 -0
  67. opensignalbox/interface/web_ui/src/components/ui/collapsible/CollapsibleContent.vue +11 -0
  68. opensignalbox/interface/web_ui/src/components/ui/collapsible/CollapsibleTrigger.vue +11 -0
  69. opensignalbox/interface/web_ui/src/components/ui/collapsible/index.ts +3 -0
  70. opensignalbox/interface/web_ui/src/components/ui/command/Command.vue +30 -0
  71. opensignalbox/interface/web_ui/src/components/ui/command/CommandDialog.vue +21 -0
  72. opensignalbox/interface/web_ui/src/components/ui/command/CommandEmpty.vue +20 -0
  73. opensignalbox/interface/web_ui/src/components/ui/command/CommandGroup.vue +29 -0
  74. opensignalbox/interface/web_ui/src/components/ui/command/CommandInput.vue +33 -0
  75. opensignalbox/interface/web_ui/src/components/ui/command/CommandItem.vue +26 -0
  76. opensignalbox/interface/web_ui/src/components/ui/command/CommandList.vue +27 -0
  77. opensignalbox/interface/web_ui/src/components/ui/command/CommandSeparator.vue +23 -0
  78. opensignalbox/interface/web_ui/src/components/ui/command/CommandShortcut.vue +14 -0
  79. opensignalbox/interface/web_ui/src/components/ui/command/index.ts +9 -0
  80. opensignalbox/interface/web_ui/src/components/ui/dialog/Dialog.vue +14 -0
  81. opensignalbox/interface/web_ui/src/components/ui/dialog/DialogClose.vue +11 -0
  82. opensignalbox/interface/web_ui/src/components/ui/dialog/DialogContent.vue +50 -0
  83. opensignalbox/interface/web_ui/src/components/ui/dialog/DialogDescription.vue +24 -0
  84. opensignalbox/interface/web_ui/src/components/ui/dialog/DialogFooter.vue +19 -0
  85. opensignalbox/interface/web_ui/src/components/ui/dialog/DialogHeader.vue +16 -0
  86. opensignalbox/interface/web_ui/src/components/ui/dialog/DialogScrollContent.vue +59 -0
  87. opensignalbox/interface/web_ui/src/components/ui/dialog/DialogTitle.vue +29 -0
  88. opensignalbox/interface/web_ui/src/components/ui/dialog/DialogTrigger.vue +11 -0
  89. opensignalbox/interface/web_ui/src/components/ui/dialog/index.ts +9 -0
  90. opensignalbox/interface/web_ui/src/components/ui/dropdown-menu/DropdownMenu.vue +14 -0
  91. opensignalbox/interface/web_ui/src/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +40 -0
  92. opensignalbox/interface/web_ui/src/components/ui/dropdown-menu/DropdownMenuContent.vue +38 -0
  93. opensignalbox/interface/web_ui/src/components/ui/dropdown-menu/DropdownMenuGroup.vue +11 -0
  94. opensignalbox/interface/web_ui/src/components/ui/dropdown-menu/DropdownMenuItem.vue +28 -0
  95. opensignalbox/interface/web_ui/src/components/ui/dropdown-menu/DropdownMenuLabel.vue +24 -0
  96. opensignalbox/interface/web_ui/src/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue +19 -0
  97. opensignalbox/interface/web_ui/src/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +41 -0
  98. opensignalbox/interface/web_ui/src/components/ui/dropdown-menu/DropdownMenuSeparator.vue +22 -0
  99. opensignalbox/interface/web_ui/src/components/ui/dropdown-menu/DropdownMenuShortcut.vue +14 -0
  100. opensignalbox/interface/web_ui/src/components/ui/dropdown-menu/DropdownMenuSub.vue +19 -0
  101. opensignalbox/interface/web_ui/src/components/ui/dropdown-menu/DropdownMenuSubContent.vue +30 -0
  102. opensignalbox/interface/web_ui/src/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +33 -0
  103. opensignalbox/interface/web_ui/src/components/ui/dropdown-menu/DropdownMenuTrigger.vue +13 -0
  104. opensignalbox/interface/web_ui/src/components/ui/dropdown-menu/index.ts +16 -0
  105. opensignalbox/interface/web_ui/src/components/ui/form/FormControl.vue +16 -0
  106. opensignalbox/interface/web_ui/src/components/ui/form/FormDescription.vue +20 -0
  107. opensignalbox/interface/web_ui/src/components/ui/form/FormItem.vue +19 -0
  108. opensignalbox/interface/web_ui/src/components/ui/form/FormLabel.vue +23 -0
  109. opensignalbox/interface/web_ui/src/components/ui/form/FormMessage.vue +16 -0
  110. opensignalbox/interface/web_ui/src/components/ui/form/index.ts +7 -0
  111. opensignalbox/interface/web_ui/src/components/ui/form/injectionKeys.ts +4 -0
  112. opensignalbox/interface/web_ui/src/components/ui/form/useFormField.ts +30 -0
  113. opensignalbox/interface/web_ui/src/components/ui/input/Input.vue +24 -0
  114. opensignalbox/interface/web_ui/src/components/ui/input/index.ts +1 -0
  115. opensignalbox/interface/web_ui/src/components/ui/label/Label.vue +27 -0
  116. opensignalbox/interface/web_ui/src/components/ui/label/index.ts +1 -0
  117. opensignalbox/interface/web_ui/src/components/ui/number-field/NumberField.vue +23 -0
  118. opensignalbox/interface/web_ui/src/components/ui/number-field/NumberFieldContent.vue +14 -0
  119. opensignalbox/interface/web_ui/src/components/ui/number-field/NumberFieldDecrement.vue +25 -0
  120. opensignalbox/interface/web_ui/src/components/ui/number-field/NumberFieldIncrement.vue +25 -0
  121. opensignalbox/interface/web_ui/src/components/ui/number-field/NumberFieldInput.vue +16 -0
  122. opensignalbox/interface/web_ui/src/components/ui/number-field/index.ts +5 -0
  123. opensignalbox/interface/web_ui/src/components/ui/popover/Popover.vue +15 -0
  124. opensignalbox/interface/web_ui/src/components/ui/popover/PopoverContent.vue +48 -0
  125. opensignalbox/interface/web_ui/src/components/ui/popover/PopoverTrigger.vue +11 -0
  126. opensignalbox/interface/web_ui/src/components/ui/popover/index.ts +3 -0
  127. opensignalbox/interface/web_ui/src/components/ui/radio-group/RadioGroup.vue +25 -0
  128. opensignalbox/interface/web_ui/src/components/ui/radio-group/RadioGroupItem.vue +39 -0
  129. opensignalbox/interface/web_ui/src/components/ui/radio-group/index.ts +2 -0
  130. opensignalbox/interface/web_ui/src/components/ui/scroll-area/ScrollArea.vue +29 -0
  131. opensignalbox/interface/web_ui/src/components/ui/scroll-area/ScrollBar.vue +30 -0
  132. opensignalbox/interface/web_ui/src/components/ui/scroll-area/index.ts +2 -0
  133. opensignalbox/interface/web_ui/src/components/ui/select/Select.vue +15 -0
  134. opensignalbox/interface/web_ui/src/components/ui/select/SelectContent.vue +53 -0
  135. opensignalbox/interface/web_ui/src/components/ui/select/SelectGroup.vue +19 -0
  136. opensignalbox/interface/web_ui/src/components/ui/select/SelectItem.vue +44 -0
  137. opensignalbox/interface/web_ui/src/components/ui/select/SelectItemText.vue +11 -0
  138. opensignalbox/interface/web_ui/src/components/ui/select/SelectLabel.vue +13 -0
  139. opensignalbox/interface/web_ui/src/components/ui/select/SelectScrollDownButton.vue +24 -0
  140. opensignalbox/interface/web_ui/src/components/ui/select/SelectScrollUpButton.vue +24 -0
  141. opensignalbox/interface/web_ui/src/components/ui/select/SelectSeparator.vue +17 -0
  142. opensignalbox/interface/web_ui/src/components/ui/select/SelectTrigger.vue +31 -0
  143. opensignalbox/interface/web_ui/src/components/ui/select/SelectValue.vue +11 -0
  144. opensignalbox/interface/web_ui/src/components/ui/select/index.ts +11 -0
  145. opensignalbox/interface/web_ui/src/components/ui/separator/Separator.vue +35 -0
  146. opensignalbox/interface/web_ui/src/components/ui/separator/index.ts +1 -0
  147. opensignalbox/interface/web_ui/src/components/ui/sheet/Sheet.vue +14 -0
  148. opensignalbox/interface/web_ui/src/components/ui/sheet/SheetClose.vue +11 -0
  149. opensignalbox/interface/web_ui/src/components/ui/sheet/SheetContent.vue +56 -0
  150. opensignalbox/interface/web_ui/src/components/ui/sheet/SheetDescription.vue +22 -0
  151. opensignalbox/interface/web_ui/src/components/ui/sheet/SheetFooter.vue +19 -0
  152. opensignalbox/interface/web_ui/src/components/ui/sheet/SheetHeader.vue +16 -0
  153. opensignalbox/interface/web_ui/src/components/ui/sheet/SheetTitle.vue +22 -0
  154. opensignalbox/interface/web_ui/src/components/ui/sheet/SheetTrigger.vue +11 -0
  155. opensignalbox/interface/web_ui/src/components/ui/sheet/index.ts +31 -0
  156. opensignalbox/interface/web_ui/src/components/ui/table/Table.vue +16 -0
  157. opensignalbox/interface/web_ui/src/components/ui/table/TableBody.vue +14 -0
  158. opensignalbox/interface/web_ui/src/components/ui/table/TableCaption.vue +14 -0
  159. opensignalbox/interface/web_ui/src/components/ui/table/TableCell.vue +21 -0
  160. opensignalbox/interface/web_ui/src/components/ui/table/TableEmpty.vue +37 -0
  161. opensignalbox/interface/web_ui/src/components/ui/table/TableFooter.vue +14 -0
  162. opensignalbox/interface/web_ui/src/components/ui/table/TableHead.vue +14 -0
  163. opensignalbox/interface/web_ui/src/components/ui/table/TableHeader.vue +14 -0
  164. opensignalbox/interface/web_ui/src/components/ui/table/TableRow.vue +14 -0
  165. opensignalbox/interface/web_ui/src/components/ui/table/index.ts +9 -0
  166. opensignalbox/interface/web_ui/src/components/ui/tabs/Tabs.vue +15 -0
  167. opensignalbox/interface/web_ui/src/components/ui/tabs/TabsContent.vue +22 -0
  168. opensignalbox/interface/web_ui/src/components/ui/tabs/TabsList.vue +25 -0
  169. opensignalbox/interface/web_ui/src/components/ui/tabs/TabsTrigger.vue +29 -0
  170. opensignalbox/interface/web_ui/src/components/ui/tabs/index.ts +4 -0
  171. opensignalbox/interface/web_ui/src/components/ui/tags-input/TagsInput.vue +22 -0
  172. opensignalbox/interface/web_ui/src/components/ui/tags-input/TagsInputInput.vue +19 -0
  173. opensignalbox/interface/web_ui/src/components/ui/tags-input/TagsInputItem.vue +22 -0
  174. opensignalbox/interface/web_ui/src/components/ui/tags-input/TagsInputItemDelete.vue +24 -0
  175. opensignalbox/interface/web_ui/src/components/ui/tags-input/TagsInputItemText.vue +19 -0
  176. opensignalbox/interface/web_ui/src/components/ui/tags-input/index.ts +5 -0
  177. opensignalbox/interface/web_ui/src/components/ui/textarea/Textarea.vue +24 -0
  178. opensignalbox/interface/web_ui/src/components/ui/textarea/index.ts +1 -0
  179. opensignalbox/interface/web_ui/src/components/ui/tooltip/Tooltip.vue +14 -0
  180. opensignalbox/interface/web_ui/src/components/ui/tooltip/TooltipContent.vue +31 -0
  181. opensignalbox/interface/web_ui/src/components/ui/tooltip/TooltipProvider.vue +11 -0
  182. opensignalbox/interface/web_ui/src/components/ui/tooltip/TooltipTrigger.vue +11 -0
  183. opensignalbox/interface/web_ui/src/components/ui/tooltip/index.ts +4 -0
  184. opensignalbox/interface/web_ui/src/libs/utils.ts +6 -0
  185. opensignalbox/interface/web_ui/src/main.ts +141 -0
  186. opensignalbox/interface/web_ui/src/views/Overview.vue +11 -0
  187. opensignalbox/interface/web_ui/src/views/Settings.vue +6 -0
  188. opensignalbox/interface/web_ui/src/views/SleafordEast.vue +205 -0
  189. opensignalbox/interface/web_ui/src/views/adapters/modbus/ModbusAdapterEdit.vue +343 -0
  190. opensignalbox/interface/web_ui/src/views/adapters/modbus/ModbusAdapterView.vue +270 -0
  191. opensignalbox/interface/web_ui/src/views/interfaces/InterfaceCreate.vue +0 -0
  192. opensignalbox/interface/web_ui/src/views/interfaces/basicio/BasicIOInterfaceEdit.vue +795 -0
  193. opensignalbox/interface/web_ui/src/views/interfaces/basicio/BasicIOInterfaceView.vue +648 -0
  194. opensignalbox/interface/web_ui/src/views/interfaces/bell/BellInterfaceEdit.vue +790 -0
  195. opensignalbox/interface/web_ui/src/views/interfaces/bell/BellInterfaceView.vue +437 -0
  196. opensignalbox/interface/web_ui/src/vite-env.d.ts +1 -0
  197. opensignalbox/interface/web_ui/tailwind.config.js +94 -0
  198. opensignalbox/interface/web_ui/tsconfig.app.json +27 -0
  199. opensignalbox/interface/web_ui/tsconfig.json +30 -0
  200. opensignalbox/interface/web_ui/tsconfig.node.json +12 -0
  201. opensignalbox/interface/web_ui/tsconfig.tsbuildinfo +1 -0
  202. opensignalbox/interface/web_ui/vite.config.d.ts +2 -0
  203. opensignalbox/interface/web_ui/vite.config.js +60 -0
  204. opensignalbox/interface/web_ui/vite.config.ts +62 -0
  205. opensignalbox_interface-0.1.0.dist-info/METADATA +49 -0
  206. opensignalbox_interface-0.1.0.dist-info/RECORD +209 -0
  207. opensignalbox_interface-0.1.0.dist-info/WHEEL +4 -0
  208. opensignalbox_interface-0.1.0.dist-info/entry_points.txt +2 -0
  209. 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,3 @@
1
+ """
2
+ Bell interface implementation.
3
+ """
@@ -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"")