libgpioddriver.bmx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483
  1. ' Copyright (c) .NET Foundation and Contributors
  2. ' Copyright (c) 2019 Bruce A Henderson
  3. '
  4. ' All rights reserved.
  5. '
  6. ' Permission is hereby granted, free of charge, to any person obtaining a copy
  7. ' of this software and associated documentation files (the "Software"), to deal
  8. ' in the Software without restriction, including without limitation the rights
  9. ' to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. ' copies of the Software, and to permit persons to whom the Software is
  11. ' furnished to do so, subject to the following conditions:
  12. '
  13. ' The above copyright notice and this permission notice shall be included in all
  14. ' copies or substantial portions of the Software.
  15. '
  16. ' THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. ' IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. ' FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. ' AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. ' LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. ' OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  22. ' SOFTWARE.
  23. '
  24. SuperStrict
  25. Import brl.threadpool
  26. Import "../gpiodriver.bmx"
  27. Import "libgpiod.bmx"
  28. Type TLibGpiodDriver Extends TGpioDriver
  29. Field chipPtr:Byte Ptr
  30. Field pinNumberToLines:TGpiodLine[]
  31. Field pinNumberToEventHandler:TLibGpiodDriverEventHandler[]
  32. Field pool:TThreadPoolExecutor
  33. Private
  34. Method New()
  35. End Method
  36. Public
  37. Method New(gpioChip:UInt = 0)
  38. If Not LibGpiodAvailable() Then
  39. Throw New TPlatformNotSupportedException()
  40. End If
  41. chipPtr = gpiod_chip_open_by_number(gpioChip)
  42. If Not chipPtr Then
  43. Throw New TIOException("no chip found")
  44. End If
  45. pool = TThreadPoolExecutor.newCachedThreadPool()
  46. Local count:Int = PinCount()
  47. pinNumberToLines = New TGpiodLine[count]
  48. pinNumberToEventHandler = New TLibGpiodDriverEventHandler[count]
  49. End Method
  50. Method PinCount:Int()
  51. Return gpiod_chip_num_lines(chipPtr)
  52. End Method
  53. Method ConvertPinNumberToLogicalNumberingScheme:Int(pinNumber:Int)
  54. ' not supported
  55. End Method
  56. Method OpenPin(pinNumber:Int)
  57. Local pin:TGpiodLine = TGpiodLine._create(gpiod_chip_get_line(chipPtr, UInt(pinNumber)))
  58. If Not pin Then
  59. Throw New TIOException("Error opening pin")
  60. End If
  61. pinNumberToLines[pinNumber] = pin
  62. End Method
  63. Method ClosePin(pinNumber:Int)
  64. Local pin:TGpiodLine = pinNumberToLines[pinNumber]
  65. If pin And Not IsListeningEvent(pinNumber) Then
  66. pin.Dispose()
  67. pinNumberToLines[pinNumber] = Null
  68. End If
  69. End Method
  70. Method SetPinMode(pinNumber:Int, pinMode:EPinMode)
  71. Local requestResult:Int = -1
  72. Local pin:TGpiodLine = pinNumberToLines[pinNumber]
  73. If pin Then
  74. If pinMode = EPinMode.Input
  75. requestResult = pin.RequestInput(String(pinNumber))
  76. Else
  77. requestResult = pin.RequestOutput(String(pinNumber))
  78. End If
  79. pin.SetPinMode(pinMode)
  80. End If
  81. If requestResult = -1 Then
  82. Throw New TIOException("Error setting pin mode : " + pinNumber)
  83. End If
  84. End Method
  85. Method GetPinMode:EPinMode(pinNumber:Int)
  86. Local pin:TGpiodLine = pinNumberToLines[pinNumber]
  87. If Not pin Then
  88. Throw New TInvalidOperationException("Pin not open : " + pinNumber)
  89. End If
  90. Return pin.GetPinMode()
  91. End Method
  92. Method IsListeningEvent:Int(pinNumber:Int)
  93. Return pinNumberToEventHandler[pinNumber] <> Null
  94. End Method
  95. Method IsPinModeSupported:Int(pinNumber:Int, pinMode:EPinMode)
  96. ' Libgpiod Api do not support pull up or pull down resistors for now.
  97. Return pinMode <> EPinMode.InputPullDown And pinMode <> EPinMode.InputPullUp
  98. End Method
  99. Method Read:EPinValue(pinNumber:Int)
  100. Local pin:TGpiodLine = pinNumberToLines[pinNumber]
  101. If pin Then
  102. Local pinValue:EPinValue
  103. Local result:Int = pin.GetValue()
  104. If result = -1 Or Not EPinValue.TryConvert(result, pinValue) Then
  105. Throw New TIOException("Read pin error : " + pinNumber)
  106. End If
  107. Return pinValue
  108. Else
  109. Throw New TInvalidOperationException("Pin not opened : " + pinNumber)
  110. End If
  111. End Method
  112. Method Write(pinNumber:Int, value:EPinValue)
  113. Local pin:TGpiodLine = pinNumberToLines[pinNumber]
  114. If Not pin Then
  115. Throw New TIOException("Pin not opened : " + pinNumber)
  116. End If
  117. If value = EPinValue.High Then
  118. pin.SetValue(1)
  119. Else
  120. pin.SetValue(0)
  121. End If
  122. End Method
  123. Method AddCallbackForPinValueChangedEvent(pinNumber:Int, eventTypes:EPinEventTypes, context:Object, callback(context:Object, sender:Object, pinValueChangedEventArgs:SPinValueChangedEventArgs))
  124. If eventTypes & EPinEventTypes.Rising Or eventTypes & EPinEventTypes.Falling Then
  125. Local eventHandler:TLibGpiodDriverEventHandler = pinNumberToEventHandler[pinNumber]
  126. If Not eventHandler Then
  127. eventHandler = PopulateEventHandler(pinNumber)
  128. End If
  129. If eventTypes & EPinEventTypes.Rising Then
  130. eventHandler.valueRising.AddLast(context, callback)
  131. End If
  132. If eventTypes & EPinEventTypes.Falling Then
  133. eventHandler.valueFalling.AddLast(context, callback)
  134. End If
  135. Else
  136. Throw New TIOException("Invalid event type")
  137. End If
  138. End Method
  139. Method PopulateEventHandler:TLibGpiodDriverEventHandler(pinNumber:Int)
  140. Local pin:TGpiodLine = pinNumberToLines[pinNumber]
  141. If pin Then
  142. If Not pin.IsFree() Then
  143. pin.Dispose()
  144. pin = TGpiodLine._create(gpiod_chip_get_line(chipPtr, UInt(pinNumber)))
  145. pinNumberToLines[pinNumber] = pin
  146. EndIf
  147. End If
  148. Return New TLibGpiodDriverEventHandler(pinNumber, pin, pool)
  149. End Method
  150. Method RemoveCallbackForPinValueChangedEvent(pinNumber:Int, callback(context:Object, sender:Object, pinValueChangedEventArgs:SPinValueChangedEventArgs))
  151. Local eventHandler:TLibGpiodDriverEventHandler = pinNumberToEventHandler[pinNumber]
  152. If eventHandler Then
  153. eventHandler.valueFalling.Remove(callback)
  154. eventHandler.ValueRising.Remove(callback)
  155. If eventHandler.IsCallbackListEmpty() Then
  156. pinNumberToEventHandler[pinNumber] = Null
  157. eventHandler.Dispose()
  158. End If
  159. Else
  160. Throw New TIOException("Not listening for event")
  161. End If
  162. End Method
  163. Method WaitForEvent()
  164. End Method
  165. Method Dispose()
  166. If pinNumberToEventHandler Then
  167. For Local i:Int = 0 Until pinNumberToEventHandler.length
  168. Local handler:TLibGpiodDriverEventHandler = pinNumberToEventHandler[i]
  169. If handler Then
  170. handler.Dispose()
  171. pinNumberToEventHandler[i] = Null
  172. End If
  173. Next
  174. pinNumberToEventHandler = Null
  175. End If
  176. If pinNumberToLines Then
  177. For Local i:Int = 0 Until pinNumberToLines.length
  178. Local pin:TGpiodLine = pinNumberToLines[i]
  179. If pin Then
  180. pin.Dispose()
  181. pinNumberToLines[i] = Null
  182. End If
  183. Next
  184. pinNumberToLines = Null
  185. End If
  186. If chipPtr Then
  187. gpiod_chip_close(chipPtr)
  188. chipPtr = Null
  189. End If
  190. End Method
  191. End Type
  192. Type TGpiodLine
  193. Field linePtr:Byte Ptr
  194. Field pinMode:EPinMode
  195. Function _create:TGpiodLine(linePtr:Byte Ptr)
  196. If linePtr Then
  197. Local this:TGpiodLine = New TGpiodLine
  198. this.linePtr = linePtr
  199. Return this
  200. End If
  201. End Function
  202. Method GetPinMode:EPinMode()
  203. Return pinMode
  204. End Method
  205. Method SetPinMode(value:EPinMode)
  206. pinMode = value
  207. End Method
  208. Method GetValue:Int()
  209. Return gpiod_line_get_value(linePtr)
  210. End Method
  211. Method SetValue:Int(value:Int)
  212. Return gpiod_line_set_value(linePtr, value)
  213. End Method
  214. Method RequestInput:Int(consumer:String)
  215. Return gpiod_line_request_input(linePtr, consumer)
  216. End Method
  217. Method RequestOutput:Int(consumer:String)
  218. Return gpiod_line_request_output(linePtr, consumer, 0)
  219. End Method
  220. Method RequestBothEdgesEvents:Int(consumer:String)
  221. Return gpiod_line_request_both_edges_events(linePtr, consumer)
  222. End Method
  223. Rem
  224. bbdoc: Waits for an event on the GPIO line.
  225. returns: 0 if wait timed out, -1 if an error occurred, 1 if an event occurred.
  226. End Rem
  227. Method EventWait:Int(timespec:STimeSpec Var)
  228. Return gpiod_line_event_wait(linePtr, timespec)
  229. End Method
  230. Rem
  231. bbdoc: Reads the last event from the GPIO line.
  232. returns: 0 if the event was read correctly, -1 on error.
  233. End Rem
  234. Method EventRead:Int(event:SGpioLineEvent Var)
  235. Return gpiod_line_event_read(linePtr, event)
  236. End Method
  237. Method IsFree:Int()
  238. Return gpiod_line_is_free(linePtr)
  239. End Method
  240. Method Dispose()
  241. If linePtr Then
  242. gpiod_line_release(linePtr)
  243. linePtr = Null
  244. End If
  245. End Method
  246. Method Delete()
  247. Dispose()
  248. End Method
  249. End Type
  250. Type TLibGpiodDriverEventHandler
  251. Field pool:TThreadPoolExecutor
  252. ' PinChangeEventHandler(object sender, PinValueChangedEventArgs pinValueChangedEventArgs
  253. Field valueRising:TCallbackList = New TCallbackList
  254. Field valueFalling:TCallbackList = New TCallbackList
  255. Field pinNumber:Int
  256. Field _shutdown:Int
  257. Method New(pinNumber:Int, pin:TGpiodLine, pool:TThreadPoolExecutor)
  258. Self.pool = pool
  259. Self.pinNumber = pinNumber
  260. ' CancellationTokenSource = new CancellationTokenSource ' TODO
  261. SubscribeForEvent(pin)
  262. InitializeEventDetectionTask(pin)
  263. End Method
  264. Method SubscribeForEvent(pin:TGpiodLine)
  265. Local eventSuccess:Int = pin.RequestBothEdgesEvents("Listen " + pinNumber + " for both edge event")
  266. If eventSuccess < 0 Then
  267. Throw New TIOException("Request event error : " + pinNumber)
  268. End If
  269. End Method
  270. Method InitializeEventDetectionTask(pin:TGpiodLine)
  271. pool.execute(New TEventDetectionTask(Self, pinNumber, pin))
  272. End Method
  273. Method OnPinValueChanged(args:SPinValueChangedEventArgs, detectionOfEventTypes:EPinEventTypes)
  274. If detectionOfEventTypes = EPinEventTypes.Rising And args.changeType = EPinEventTypes.Rising Then
  275. If valueRising.size Then
  276. For Local i:Int = 0 Until valueRising.size
  277. Local cb:TCallback = valueRising.data[i]
  278. cb.data(cb.context, Self, args)
  279. Next
  280. End If
  281. Else
  282. If valueFalling.size Then
  283. For Local i:Int = 0 Until valueFalling.size
  284. Local cb:TCallback = valueFalling.data[i]
  285. cb.data(cb.context, Self, args)
  286. Next
  287. End If
  288. End If
  289. End Method
  290. Method IsCallbackListEmpty:Int()
  291. Return Not valueRising.size And Not valueFalling.size
  292. End Method
  293. Method Dispose()
  294. _shutdown = True
  295. End Method
  296. End Type
  297. Type TEventDetectionTask Extends TRunnable
  298. Field eventHandler:TLibGpiodDriverEventHandler
  299. Field pinNumber:Int
  300. Field pin:TGpiodLine
  301. Field timespec:STimeSpec
  302. Method New(eventHandler:TLibGpiodDriverEventHandler, pinNumber:Int, pin:TGpiodLine)
  303. Self.eventHandler = eventHandler
  304. Self.pinNumber = pinNumber
  305. Self.pin = pin
  306. timespec = New STimeSpec(0, 1000000)
  307. End Method
  308. Method Run()
  309. While Not eventHandler._shutdown
  310. Local res:Int = pin.EventWait(timespec)
  311. ' error
  312. If res = -1 Then
  313. Throw New TIOException("Event wait error " + pinNumber)
  314. End If
  315. ' event
  316. If res = 1 Then
  317. Local event:SGpioLineEvent
  318. If pin.EventRead(event) = -1 Then
  319. Throw New TIOException("Event read error " + pinNumber)
  320. End If
  321. eventHandler.OnPinValueChanged(New SPinValueChangedEventArgs(event.AsPinEventType(), pinNumber), event.AsPinEventType())
  322. End If
  323. Wend
  324. End Method
  325. End Type
  326. Type TCallback
  327. Field context:Object
  328. Field data(context:Object, sender:Object, pinValueChangedEventArgs:SPinValueChangedEventArgs)
  329. Method New(context:Object, data(context:Object, sender:Object, pinValueChangedEventArgs:SPinValueChangedEventArgs))
  330. Self.context = context
  331. Self.data = data
  332. End Method
  333. End Type
  334. Type TCallbackList
  335. Field data:TCallback[16]
  336. Field size:Int
  337. Method _ensureCapacity(newSize:Int)
  338. If newSize >= data.length Then
  339. data = data[.. newSize * 3 / 2 + 1]
  340. End If
  341. End Method
  342. Method Clear()
  343. For Local i:Int = 0 Until size
  344. data[i] = Null
  345. Next
  346. size = 0
  347. End Method
  348. Method IsEmpty:Int()
  349. Return size = 0
  350. End Method
  351. Method AddLast(context:Object, callback(context:Object, sender:Object, pinValueChangedEventArgs:SPinValueChangedEventArgs))
  352. _ensureCapacity(size + 1)
  353. data[size] = New TCallback(context, callback)
  354. size :+ 1
  355. End Method
  356. Method Remove(callback(context:Object, sender:Object, pinValueChangedEventArgs:SPinValueChangedEventArgs))
  357. If size Then
  358. Local offset:Int = -1
  359. For Local i:Int = 0 Until size
  360. If data[i].data = callback Then
  361. offset = i
  362. End If
  363. Next
  364. If offset >= 0 Then
  365. Local length:Int = size - offset
  366. If length > 0 Then
  367. ArrayCopy(data, offset + 1, data, offset, length)
  368. End If
  369. size :- 1
  370. data[size] = Null
  371. End If
  372. End If
  373. End Method
  374. Method _removeAt(index:Int)
  375. data[index] = Null
  376. End Method
  377. End Type