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,187 @@
1
+ """
2
+ Main module for opensignalbox-interface.
3
+ """
4
+
5
+ import logging
6
+
7
+ from anyio import run, sleep
8
+ from opensignalbox.common.messaging import get_messager
9
+ from opensignalbox.common.modules import (
10
+ BaseModule,
11
+ ModuleInfo,
12
+ ModuleStatusEnum,
13
+ UIEntry,
14
+ module_argparser,
15
+ )
16
+ from opensignalbox.interface.adapters.controller import AdapterController
17
+ from opensignalbox.interface.interfaces.controller import InterfaceController
18
+
19
+ from .routes import adapter_router, interface_router
20
+ from .version import __version__
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ messager = get_messager()
26
+
27
+
28
+ class Module(BaseModule):
29
+ """OSB Interface Module."""
30
+
31
+ module_name = "opensignalbox-interface"
32
+ module_file = __file__
33
+
34
+ def __init__(
35
+ self,
36
+ module_info: ModuleInfo,
37
+ ui_entries: list[UIEntry] | None = None,
38
+ ) -> None:
39
+ if ui_entries is None:
40
+ ui_entries = []
41
+ super().__init__(
42
+ module_info=module_info,
43
+ ui_entries=ui_entries,
44
+ )
45
+
46
+ # Initialize controllers
47
+ self.adapter_controller = AdapterController(self.module_info.config_path)
48
+ self.fastapi_app.state.adapter_controller = self.adapter_controller
49
+ self.interface_controller = InterfaceController(self.module_info.config_path, self.shared_variables, self.adapter_controller)
50
+ self.fastapi_app.state.interface_controller = self.interface_controller
51
+
52
+ # Include API routers
53
+ self.fastapi_app.include_router(
54
+ adapter_router, prefix="/api/adapters",
55
+ tags=["Adapters"]
56
+ )
57
+ self.fastapi_app.include_router(
58
+ interface_router, prefix="/api/interfaces",
59
+ tags=["Interfaces"]
60
+ )
61
+
62
+ def setup(self) -> None:
63
+ """
64
+ Module startup code.
65
+ """
66
+ # Register any interface handlers and adapter factories
67
+ self._register_handlers()
68
+
69
+ # Register interface-specific routers
70
+ self._register_interface_routers()
71
+
72
+ # Register adapter-specific routers
73
+ self._register_adapter_routers()
74
+
75
+ # Load configurations
76
+ self.adapter_controller.load_from_json()
77
+ self.interface_controller.load_from_json()
78
+
79
+ # Connect interfaces
80
+ if self.interface_controller.module_config.connect_on_startup:
81
+ self.interface_controller.connect_all()
82
+
83
+ return super().setup()
84
+
85
+ def _register_interface_routers(self) -> None:
86
+ """Register interface-specific routers."""
87
+ # Get all interface routers and include them in FastAPI app
88
+ for interface_type, router in self.interface_controller.get_interface_routers().items():
89
+ # Convert enum to lowercase string for URL path
90
+ interface_path = interface_type.value.lower()
91
+ self.fastapi_app.include_router(
92
+ router, prefix=f"/api/interfaces/{interface_path}"
93
+ )
94
+
95
+ def _register_adapter_routers(self) -> None:
96
+ """Register adapter-specific routers."""
97
+ # Get all adapter routers and include them in FastAPI app
98
+ for adapter_type, router in self.adapter_controller.get_adapter_routers().items():
99
+ # Convert enum to lowercase string for URL path
100
+ adapter_path = adapter_type.value.lower()
101
+ self.fastapi_app.include_router(
102
+ router, prefix=f"/api/adapters/{adapter_path}"
103
+ )
104
+
105
+ def _register_handlers(self) -> None:
106
+ """Register interface handlers and adapter factories."""
107
+ self.interface_controller.register_interface_handlers()
108
+ self.adapter_controller.register_adapter_factories()
109
+
110
+ def systick_update(self, tick_json: str) -> None:
111
+ """
112
+ Module update logic executed once per clock tick.
113
+ """
114
+ super().systick_update(tick_json)
115
+ if self.interface_controller.module_config.sync_on_systick:
116
+ self.interface_controller.sync_interfaces()
117
+
118
+ async def user_task(self) -> None:
119
+ """Background task for periodic interface updates."""
120
+ while True:
121
+ if (
122
+ not self.interface_controller.module_config.sync_on_systick
123
+ and self.module_info.status == ModuleStatusEnum.READY
124
+ ):
125
+ await self.interface_controller.tick()
126
+ else:
127
+ await sleep(1)
128
+
129
+ def teardown(self) -> None:
130
+ """
131
+ Module teardown code.
132
+ """
133
+ # Disconnect interfaces and adapters
134
+ self.interface_controller.disconnect_all()
135
+ self.adapter_controller.disconnect_all()
136
+
137
+ # Save configurations
138
+ self.adapter_controller.save_to_json()
139
+ self.interface_controller.save_to_json()
140
+
141
+ def load_state(self) -> None:
142
+ """
143
+ Load module state.
144
+ """
145
+ return super().load_state()
146
+
147
+ def save_state(self) -> None:
148
+ """
149
+ Save module state.
150
+ """
151
+ return super().save_state()
152
+
153
+
154
+ args = module_argparser()
155
+ instance_name: str = args.name
156
+
157
+ ui_entries = [
158
+ UIEntry(
159
+ category="operations", subcategory="Interfaces", name="Interface List", path="/"
160
+ ),
161
+ UIEntry(
162
+ category="configuration",
163
+ subcategory="Interfaces",
164
+ name="Interface Settings",
165
+ path="/settings",
166
+ ),
167
+ ]
168
+ module_info = ModuleInfo(
169
+ instance_name=args.name,
170
+ module_name=Module.module_name,
171
+ version=__version__,
172
+ address=args.address,
173
+ port=args.port,
174
+ session_id=args.session_id,
175
+ framework_address=args.framework_address,
176
+ config_path=args.configdir,
177
+ )
178
+ module = Module(module_info=module_info, ui_entries=ui_entries)
179
+ module.setup()
180
+
181
+
182
+ def entry_point():
183
+ run(module.run, backend="trio") # type: ignore
184
+
185
+
186
+ if __name__ == "__main__":
187
+ entry_point()
@@ -0,0 +1,155 @@
1
+ """
2
+ Base models for the opensignalbox-interface module.
3
+ """
4
+
5
+ from enum import Enum
6
+ from typing import List, Optional
7
+
8
+ from opensignalbox.common.models import CamelModel
9
+
10
+
11
+ class InterfaceTypeEnum(str, Enum):
12
+ """Enum for interface types"""
13
+
14
+ BASICIO = "BasicIO"
15
+ BELL = "Bell"
16
+ # Add more interface types here as needed
17
+
18
+
19
+ class AdapterTypeEnum(str, Enum):
20
+ """Enum for adapter types"""
21
+
22
+ MODBUS = "Modbus"
23
+ # Add more adapter types here as needed
24
+
25
+
26
+ class ModuleConfig(CamelModel):
27
+ input_update_period: float = 0.1
28
+ output_update_period: float = 1
29
+ sync_on_systick: bool = False
30
+ connect_on_startup: bool = True
31
+
32
+
33
+ class AdapterBase(CamelModel):
34
+ """Base model for all adapters"""
35
+
36
+ system_name: str
37
+ user_name: str
38
+ description: str = ""
39
+ adapter_type: AdapterTypeEnum
40
+ enabled: bool = True
41
+ error: Optional[str] = None
42
+ # Dynamic fields for different adapter types
43
+ is_network: Optional[bool] = None
44
+ serial_port: Optional[str] = None
45
+ baud: Optional[int] = None
46
+ address: Optional[str] = None
47
+ network_port: Optional[int] = None
48
+
49
+
50
+ class AdapterModel(AdapterBase):
51
+ """Runtime model for adapters with additional operational fields"""
52
+
53
+ is_connected: bool = False
54
+
55
+
56
+ class AdapterCreate(CamelModel):
57
+ """API model for creating adapters. Omits adapter type."""
58
+
59
+ system_name: str
60
+ user_name: str
61
+ description: str = ""
62
+ enabled: bool = True
63
+ error: Optional[str] = None
64
+ # Dynamic fields for different adapter types
65
+ is_network: Optional[bool] = None
66
+ serial_port: Optional[str] = None
67
+ baud: Optional[int] = None
68
+ address: Optional[str] = None
69
+ network_port: Optional[int] = None
70
+
71
+
72
+ class AdapterPublic(AdapterBase):
73
+ """API model for adapter responses"""
74
+
75
+ is_connected: bool = False
76
+
77
+
78
+ class AdapterUpdate(CamelModel):
79
+ """API model for adapter updates"""
80
+
81
+ system_name: Optional[str] = None
82
+ user_name: Optional[str] = None
83
+ description: Optional[str] = None
84
+ enabled: Optional[bool] = None
85
+ is_network: Optional[bool] = None
86
+ serial_port: Optional[str] = None
87
+ baud: Optional[int] = None
88
+ address: Optional[str] = None
89
+ network_port: Optional[int] = None
90
+
91
+
92
+ class InterfaceBase(CamelModel):
93
+ """Base model for all interfaces with common fields"""
94
+
95
+ system_name: str
96
+ user_name: str
97
+ description: str = ""
98
+ adapter_name: Optional[str] = None
99
+ error: Optional[str] = None
100
+ enabled: bool = True
101
+
102
+
103
+ class Interface(InterfaceBase):
104
+ """Runtime model for all interfaces"""
105
+
106
+ interface_type: InterfaceTypeEnum
107
+ is_connected: bool = False
108
+
109
+
110
+ class InterfacePublic(Interface):
111
+ """API model for interface responses"""
112
+
113
+ is_connected: bool = False
114
+
115
+
116
+ class InterfaceUpdate(CamelModel):
117
+ """API model for interface updates"""
118
+
119
+ system_name: Optional[str] = None
120
+ user_name: Optional[str] = None
121
+ description: Optional[str] = None
122
+ adapter_name: Optional[str] = None
123
+ enabled: Optional[bool] = None
124
+
125
+
126
+ class ConnectionTypeInfo(CamelModel):
127
+ """Information about a connection type"""
128
+
129
+ interface_type: InterfaceTypeEnum
130
+ adapter_type: AdapterTypeEnum
131
+ connection_class: str
132
+ name: str
133
+ description: str
134
+
135
+
136
+ class AdapterTypeInfo(CamelModel):
137
+ """Information about an adapter type"""
138
+
139
+ adapter_type: AdapterTypeEnum
140
+ name: str
141
+ description: str
142
+ supported_connection_types: List[str]
143
+ required_fields: List[str]
144
+ optional_fields: List[str]
145
+
146
+
147
+ class InterfaceTypeInfo(CamelModel):
148
+ """Information about an interface type"""
149
+
150
+ interface_type: InterfaceTypeEnum
151
+ name: str
152
+ description: str
153
+ supported_connection_types: List[str]
154
+ required_fields: List[str]
155
+ optional_fields: List[str]
@@ -0,0 +1,4 @@
1
+ from .adapters import adapter_router
2
+ from .interfaces import interface_router
3
+
4
+ __all__ = ["interface_router", "adapter_router"]
@@ -0,0 +1,119 @@
1
+ """
2
+ API routes for the opensignalbox.interface module.
3
+ """
4
+
5
+ import logging
6
+ from typing import Annotated, List, Optional
7
+
8
+ from fastapi import APIRouter, Depends, HTTPException, Query, Request
9
+ from opensignalbox.common.variables import SharedVariableCollection
10
+ from opensignalbox.interface.adapters.controller import AdapterController
11
+ from opensignalbox.interface.models import (
12
+ AdapterPublic,
13
+ AdapterTypeInfo,
14
+ )
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+ def get_adapter_controller(request: Request) -> AdapterController:
19
+ return request.app.state.adapter_controller
20
+
21
+ AdapterControllerDependency = Annotated[AdapterController, Depends(get_adapter_controller)]
22
+
23
+ def get_shared_variables(request: Request) -> SharedVariableCollection:
24
+ return request.app.state.shared_variables
25
+
26
+ SharedVariablesDependency = Annotated[SharedVariableCollection, Depends(get_shared_variables)]
27
+
28
+ adapter_router = APIRouter()
29
+
30
+ @adapter_router.get("/", response_model=List[AdapterPublic])
31
+ async def get_adapters(
32
+ adapters: AdapterControllerDependency,
33
+ adapter_type: Optional[str] = Query(
34
+ None, description="Filter by adapter type"
35
+ ),
36
+ ):
37
+ """Get all adapters, optionally filtered by type."""
38
+ adapters_list = []
39
+ for adapter in adapters.get_all().values():
40
+ # Filter by type if specified
41
+ if adapter_type and adapter.adapter_type != adapter_type:
42
+ continue
43
+
44
+ # Convert BaseAdapter to AdapterPublic
45
+ adapter_dict = {
46
+ "system_name": adapter.system_name,
47
+ "user_name": adapter.user_name,
48
+ "description": adapter.description,
49
+ "adapter_type": adapter.adapter_type,
50
+ "enabled": adapter.enabled,
51
+ "is_connected": adapter.is_connected,
52
+ }
53
+ adapters_list.append(AdapterPublic(**adapter_dict))
54
+ return adapters_list
55
+
56
+ @adapter_router.put("/{system_name}/enable", response_model=AdapterPublic)
57
+ async def enable_adapter(system_name: str, adapters: AdapterControllerDependency):
58
+ """Enable a specific adapter."""
59
+ adapter = adapters.get(system_name)
60
+ if not adapter:
61
+ raise HTTPException(status_code=404, detail="Adapter not found")
62
+ adapter.enabled = True
63
+ adapter_dict = {
64
+ "system_name": adapter.system_name,
65
+ "user_name": adapter.user_name,
66
+ "description": adapter.description,
67
+ "adapter_type": adapter.adapter_type,
68
+ "enabled": adapter.enabled,
69
+ "is_connected": adapter.is_connected,
70
+ }
71
+ return AdapterPublic(**adapter_dict)
72
+
73
+ @adapter_router.put(
74
+ "/{system_name}/disable", response_model=AdapterPublic
75
+ )
76
+ async def disable_adapter(system_name: str, adapters: AdapterControllerDependency):
77
+ """Disable a specific adapter."""
78
+ adapter = adapters.get(system_name)
79
+ if not adapter:
80
+ raise HTTPException(status_code=404, detail="Adapter not found")
81
+ adapter.enabled = False
82
+ adapter_dict = {
83
+ "system_name": adapter.system_name,
84
+ "user_name": adapter.user_name,
85
+ "description": adapter.description,
86
+ "adapter_type": adapter.adapter_type,
87
+ "enabled": adapter.enabled,
88
+ "is_connected": adapter.is_connected,
89
+ }
90
+ return AdapterPublic(**adapter_dict)
91
+
92
+ @adapter_router.put(
93
+ "/{system_name}/connect", response_model=AdapterPublic
94
+ )
95
+ async def connect_adapter(system_name: str, adapters: AdapterControllerDependency):
96
+ """Connect a specific adapter."""
97
+ adapter = adapters.get(system_name)
98
+ if not adapter:
99
+ raise HTTPException(status_code=404, detail="Adapter not found")
100
+ if adapter.connect():
101
+ return adapter
102
+ else:
103
+ raise HTTPException(status_code=500, detail="Failed to connect adapter")
104
+
105
+ @adapter_router.put(
106
+ "/{system_name}/disconnect", response_model=AdapterPublic
107
+ )
108
+ async def disconnect_adapter(system_name: str, adapters: AdapterControllerDependency):
109
+ """Disconnect a specific adapter."""
110
+ adapter = adapters.get(system_name)
111
+ if not adapter:
112
+ raise HTTPException(status_code=404, detail="Adapter not found")
113
+ adapter.disconnect()
114
+ return adapter
115
+
116
+ @adapter_router.get("/types/adapters", response_model=List[AdapterTypeInfo])
117
+ async def get_adapter_types(adapters: AdapterControllerDependency):
118
+ """Get all available adapter types with their metadata."""
119
+ return adapters.get_adapter_type_infos()
@@ -0,0 +1,104 @@
1
+ """
2
+ API routes for the opensignalbox-interface module.
3
+ """
4
+
5
+ import logging
6
+ from typing import Annotated, List, Optional
7
+
8
+ from fastapi import APIRouter, Depends, HTTPException, Query, Request
9
+ from opensignalbox.common.variables import SharedVariableCollection
10
+ from opensignalbox.interface.interfaces.controller import InterfaceController
11
+ from opensignalbox.interface.models import (
12
+ InterfacePublic,
13
+ InterfaceTypeInfo,
14
+ )
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+ def get_interface_controller(request: Request) -> InterfaceController:
19
+ return request.app.state.interface_controller
20
+
21
+ InterfaceControllerDependency = Annotated[InterfaceController, Depends(get_interface_controller)]
22
+
23
+ def get_shared_variables(request: Request) -> SharedVariableCollection:
24
+ return request.app.state.shared_variables
25
+
26
+ SharedVariablesDependency = Annotated[SharedVariableCollection, Depends(get_shared_variables)]
27
+
28
+ interface_router = APIRouter()
29
+
30
+ @interface_router.get("/", response_model=List[InterfacePublic])
31
+ async def get_interfaces(
32
+ interfaces: InterfaceControllerDependency,
33
+ interface_type: Optional[str] = Query(
34
+ None, description="Filter by interface type"
35
+ ),
36
+ ):
37
+ """Get all interfaces, optionally filtered by type."""
38
+ interfaces_list = []
39
+ for interface in interfaces.get_all().values():
40
+ # Filter by type if specified
41
+ if interface_type and interface.interface_type != interface_type:
42
+ continue
43
+
44
+ interfaces_list.append(interface)
45
+ return interfaces_list
46
+
47
+ @interface_router.put(
48
+ "/{system_name}/disable", response_model=InterfacePublic
49
+ )
50
+ async def disable_interface(system_name: str, interfaces: InterfaceControllerDependency):
51
+ """Disable a specific interface."""
52
+ interface = interfaces.get(system_name)
53
+ if not interface:
54
+ raise HTTPException(status_code=404, detail="Interface not found")
55
+ interface.enabled = False
56
+ return interface
57
+
58
+ @interface_router.put(
59
+ "/{system_name}/enable", response_model=InterfacePublic
60
+ )
61
+ async def enable_interface(system_name: str, interfaces: InterfaceControllerDependency):
62
+ """Enable a specific interface."""
63
+ interface = interfaces.get(system_name)
64
+ if not interface:
65
+ raise HTTPException(status_code=404, detail="Interface not found")
66
+ interface.enabled = True
67
+ return interface
68
+
69
+ @interface_router.put(
70
+ "/{system_name}/connect", response_model=InterfacePublic
71
+ )
72
+ async def connect_interface(system_name: str, interfaces: InterfaceControllerDependency):
73
+ """Connect a specific interface."""
74
+ interface = interfaces.get(system_name)
75
+ if not interface:
76
+ raise HTTPException(status_code=404, detail="Interface not found")
77
+ handler = interfaces.interface_handlers.get(interface.interface_type)
78
+ if handler and handler.connect(interface):
79
+ return interface
80
+ else:
81
+ raise HTTPException(
82
+ status_code=500, detail="Failed to connect interface"
83
+ )
84
+
85
+ @interface_router.put(
86
+ "/{system_name}/disconnect", response_model=InterfacePublic
87
+ )
88
+ async def disconnect_interface(system_name: str, interfaces: InterfaceControllerDependency):
89
+ """Disconnect a specific interface."""
90
+ interface = interfaces.get(system_name)
91
+ if not interface:
92
+ raise HTTPException(status_code=404, detail="Interface not found")
93
+ handler = interfaces.interface_handlers.get(interface.interface_type)
94
+ if handler and handler.disconnect(interface):
95
+ return interface
96
+ else:
97
+ raise HTTPException(
98
+ status_code=500, detail="Failed to disconnect interface"
99
+ )
100
+
101
+ @interface_router.get("/types/interfaces", response_model=List[InterfaceTypeInfo])
102
+ async def get_interface_types(interfaces: InterfaceControllerDependency):
103
+ """Get all available interface types with their metadata."""
104
+ return interfaces.get_interface_type_infos()