42 Commits

Auteur SHA1 Bericht Datum
  Thomas Buck 965cf087d8 print current value in workflow 10 maanden geleden
  Thomas Buck ca91fdf394 only call disconnect once when aborting workflow 10 maanden geleden
  Thomas Buck 33773f52d6 print workflow step and allow aborting 10 maanden geleden
  Thomas Buck 0c2780e7ae allow going back from workflow selection 10 maanden geleden
  Thomas Buck 5af9bc8780 add simplistic crafty support 10 maanden geleden
  Thomas Buck 01259255e8 clear text box bg. fix ble disconnect and workflow notify. 10 maanden geleden
  Thomas Buck e78ad09f27 add simple volcano_run state 10 maanden geleden
  Thomas Buck f064eb3a5a generalize menu infrastructure to also use it for workflow selection 10 maanden geleden
  Thomas Buck 5063ba3de1 add workflow logic 10 maanden geleden
  Thomas Buck 00498af11e device selection menu 10 maanden geleden
  Thomas Buck 0769496400 only show relevant devices in scan state 10 maanden geleden
  Thomas Buck 00e30acf5d detect known device types and read their serial number 10 maanden geleden
  Thomas Buck 03daa0ec1e test workflow in debug console 10 maanden geleden
  Thomas Buck 722d6e6d8f add commands for heater and pump state 10 maanden geleden
  Thomas Buck c3a43c4f91 proper timeouts for ble write, now working. 10 maanden geleden
  Thomas Buck 46f9c9a5da serial console mostly working, but still losing some bytes when transmitting lots. 10 maanden geleden
  Thomas Buck 3b40fe0f43 option to block on uart buffer overrun 10 maanden geleden
  Thomas Buck e41bd0bfed add generic ring buffer lib, used for log buffer and new uart tx buffer. uart as second console option for nicer debugging. 10 maanden geleden
  Thomas Buck 395ef608e6 implement ble service and characteristic discovery and value write for volcano. still crashing unfortunately. 10 maanden geleden
  Thomas Buck c6da7c81a2 make inclusion of sources optional 10 maanden geleden
  Thomas Buck 2354f36e7a tweak debug disk creation 10 maanden geleden
  Thomas Buck 558224aa67 have the device give out its own source code 10 maanden geleden
  Thomas Buck 17c4cf73ba run state machine for testing periodically. 10 maanden geleden
  Thomas Buck 0d8e77dd69 optional auto connect for volcano console commands. 10 maanden geleden
  Thomas Buck 1b3e3e286b implement ble read 10 maanden geleden
  Thomas Buck 3f60298a6e only redraw battery indicator when needed 10 maanden geleden
  Thomas Buck c376ba1486 ble connect and disconnect 10 maanden geleden
  Thomas Buck 2c330fd03f basic state machine for app logic 10 maanden geleden
  Thomas Buck d2c67b7c92 gap inquiry scan is not needed after all 10 maanden geleden
  Thomas Buck 8e13f7b0d8 more work on ble scan. still not finding target device name, even with gap inquiry. 10 maanden geleden
  Thomas Buck 822302d3e8 support arbitrary blended text colors 10 maanden geleden
  Thomas Buck b7bae8f489 draw battery status on display 10 maanden geleden
  Thomas Buck d7f8847dab optional background fill for text, for splash screen 10 maanden geleden
  Thomas Buck 6a4c2b8e16 first text rendering test 10 maanden geleden
  Thomas Buck 86e238560d lcd working, drawing logo as splash screen 10 maanden geleden
  Thomas Buck 47d270deb2 add ble scanning test 10 maanden geleden
  Thomas Buck 25aa485d8f add lipo shim reading 10 maanden geleden
  Thomas Buck a9bf35e24b pico w onboard led 10 maanden geleden
  Thomas Buck 0b7fcb080b first building but not running state of C Pico SDK version 10 maanden geleden
  Thomas Buck 76436adf41 unfinished py ota 10 maanden geleden
  Thomas Buck 8c9fcb659d modify brightness in menus. nicer battery display. 10 maanden geleden
  Thomas Buck 18b4c98c5e gpl license 10 maanden geleden
93 gewijzigde bestanden met toevoegingen van 11958 en 31 verwijderingen
  1. 7
    0
      .gitignore
  2. 12
    0
      .gitmodules
  3. 145
    0
      CMakeLists.txt
  4. 674
    0
      COPYING
  5. 131
    0
      README.md
  6. 33
    0
      case/case.scad
  7. 245
    0
      case/pico_case.scad
  8. 18
    0
      debug.sh
  9. 31
    0
      debug_swd.sh
  10. 1
    0
      fatfs
  11. 28
    0
      flash.sh
  12. 6
    0
      flash_swd.sh
  13. 61
    0
      include/ble.h
  14. 58
    0
      include/btstack_config.h
  15. 42
    0
      include/buttons.h
  16. 48
    0
      include/config.h
  17. 26
    0
      include/console.h
  18. 38
    0
      include/crafty.h
  19. 27
    0
      include/debug_disk.h
  20. 21
    0
      include/fat_disk.h
  21. 296
    0
      include/ffconf.h
  22. 30
    0
      include/image.h
  23. 44
    0
      include/lcd.h
  24. 26
    0
      include/lipo.h
  25. 48
    0
      include/log.h
  26. 3615
    0
      include/logo.h
  27. 24
    0
      include/main.h
  28. 37
    0
      include/menu.h
  29. 33
    0
      include/models.h
  30. 43
    0
      include/ring.h
  31. 31
    0
      include/serial.h
  32. 33
    0
      include/state.h
  33. 30
    0
      include/state_crafty.h
  34. 26
    0
      include/state_scan.h
  35. 31
    0
      include/state_volcano_run.h
  36. 26
    0
      include/state_volcano_workflow.h
  37. 53
    0
      include/text.h
  38. 125
    0
      include/tusb_config.h
  39. 25
    0
      include/usb.h
  40. 29
    0
      include/usb_cdc.h
  41. 24
    0
      include/usb_descriptors.h
  42. 25
    0
      include/usb_msc.h
  43. 36
    0
      include/util.h
  44. 36
    0
      include/volcano.h
  45. 60
    0
      include/workflow.h
  46. 1
    0
      mcufont
  47. 22
    0
      pack_data.sh
  48. 1
    0
      pico-sdk
  49. 16
    0
      python-test/copy.sh
  50. 48
    2
      python-test/lcd.py
  51. 187
    0
      python-test/ota.py
  52. 17
    1
      python-test/poll.py
  53. 18
    0
      python-test/scan.py
  54. 17
    1
      python-test/state_connect.py
  55. 17
    1
      python-test/state_heat.py
  56. 17
    1
      python-test/state_notify.py
  57. 17
    1
      python-test/state_pump.py
  58. 34
    2
      python-test/state_scan.py
  59. 29
    2
      python-test/state_select.py
  60. 17
    1
      python-test/state_wait_temp.py
  61. 17
    1
      python-test/state_wait_time.py
  62. 59
    17
      python-test/states.py
  63. 17
    1
      python-test/workflows.py
  64. 770
    0
      src/ble.c
  65. 80
    0
      src/buttons.c
  66. 552
    0
      src/console.c
  67. 121
    0
      src/crafty.c
  68. 147
    0
      src/debug_disk.c
  69. 130
    0
      src/fat_disk.c
  70. 152
    0
      src/image.c
  71. 309
    0
      src/lcd.c
  72. 120
    0
      src/lipo.c
  73. 142
    0
      src/log.c
  74. 112
    0
      src/main.c
  75. 105
    0
      src/menu.c
  76. 53
    0
      src/models.c
  77. 92
    0
      src/ring.c
  78. 157
    0
      src/serial.c
  79. 115
    0
      src/state.c
  80. 127
    0
      src/state_crafty.c
  81. 132
    0
      src/state_scan.c
  82. 146
    0
      src/state_volcano_run.c
  83. 78
    0
      src/state_volcano_workflow.c
  84. 192
    0
      src/text.c
  85. 70
    0
      src/usb.c
  86. 115
    0
      src/usb_cdc.c
  87. 229
    0
      src/usb_descriptors.c
  88. 215
    0
      src/usb_msc.c
  89. 109
    0
      src/util.c
  90. 118
    0
      src/volcano.c
  91. 264
    0
      src/workflow.c
  92. 1
    0
      st7789
  93. 16
    0
      web-app/fetch.sh

+ 7
- 0
.gitignore Bestand weergeven

@@ -1,2 +1,9 @@
1 1
 web-app/*.js
2
+web-app/*/*.js
2 3
 __pycache__
4
+build
5
+build_debug
6
+*.stl
7
+*.sl1
8
+*.wow
9
+.directory

+ 12
- 0
.gitmodules Bestand weergeven

@@ -0,0 +1,12 @@
1
+[submodule "pico-sdk"]
2
+	path = pico-sdk
3
+	url = https://github.com/raspberrypi/pico-sdk
4
+[submodule "fatfs"]
5
+	path = fatfs
6
+	url = https://github.com/abbrev/fatfs
7
+[submodule "mcufont"]
8
+	path = mcufont
9
+	url = https://github.com/mcufont/mcufont
10
+[submodule "st7789"]
11
+	path = st7789
12
+	url = https://github.com/hepingood/st7789

+ 145
- 0
CMakeLists.txt Bestand weergeven

@@ -0,0 +1,145 @@
1
+cmake_minimum_required(VERSION 3.13)
2
+
3
+# build MCUFont encoder host tool and convert included example fonts
4
+execute_process(COMMAND make
5
+    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/mcufont/encoder
6
+)
7
+execute_process(COMMAND make
8
+    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/mcufont/fonts
9
+)
10
+
11
+# initialize pico-sdk from submodule
12
+include(pico-sdk/pico_sdk_init.cmake)
13
+
14
+project(gadget C CXX)
15
+set(CMAKE_C_STANDARD 11)
16
+set(CMAKE_CXX_STANDARD 17)
17
+
18
+# initialize the Raspberry Pi Pico SDK
19
+pico_sdk_init()
20
+
21
+# copy FatFS source files to build dir, so we can use our own ffconf.h
22
+configure_file(
23
+    ${CMAKE_CURRENT_SOURCE_DIR}/fatfs/source/ff.c
24
+    ${CMAKE_CURRENT_BINARY_DIR}/fatfs/ff.c
25
+    COPYONLY
26
+)
27
+configure_file(
28
+    ${CMAKE_CURRENT_SOURCE_DIR}/fatfs/source/ff.h
29
+    ${CMAKE_CURRENT_BINARY_DIR}/fatfs/ff.h
30
+    COPYONLY
31
+)
32
+configure_file(
33
+    ${CMAKE_CURRENT_SOURCE_DIR}/fatfs/source/diskio.h
34
+    ${CMAKE_CURRENT_BINARY_DIR}/fatfs/diskio.h
35
+    COPYONLY
36
+)
37
+configure_file(
38
+    ${CMAKE_CURRENT_SOURCE_DIR}/fatfs/source/ffunicode.c
39
+    ${CMAKE_CURRENT_BINARY_DIR}/fatfs/ffunicode.c
40
+    COPYONLY
41
+)
42
+
43
+add_executable(gadget)
44
+
45
+target_sources(gadget PUBLIC
46
+    src/main.c
47
+    src/console.c
48
+    src/log.c
49
+    src/util.c
50
+    src/usb.c
51
+    src/usb_cdc.c
52
+    src/usb_descriptors.c
53
+    src/usb_msc.c
54
+    src/fat_disk.c
55
+    src/debug_disk.c
56
+    src/buttons.c
57
+    src/lipo.c
58
+    src/ble.c
59
+    src/lcd.c
60
+    src/text.c
61
+    src/image.c
62
+    src/state.c
63
+    src/volcano.c
64
+    src/serial.c
65
+    src/ring.c
66
+    src/models.c
67
+    src/state_scan.c
68
+    src/workflow.c
69
+    src/menu.c
70
+    src/state_volcano_workflow.c
71
+    src/state_volcano_run.c
72
+    src/crafty.c
73
+    src/state_crafty.c
74
+
75
+    ${CMAKE_CURRENT_BINARY_DIR}/fatfs/ff.c
76
+    ${CMAKE_CURRENT_BINARY_DIR}/fatfs/ffunicode.c
77
+
78
+    st7789/src/driver_st7789.c
79
+
80
+    mcufont/decoder/mf_encoding.c
81
+    mcufont/decoder/mf_font.c
82
+    mcufont/decoder/mf_justify.c
83
+    mcufont/decoder/mf_kerning.c
84
+    mcufont/decoder/mf_rlefont.c
85
+    mcufont/decoder/mf_bwfont.c
86
+    mcufont/decoder/mf_scaledfont.c
87
+    mcufont/decoder/mf_wordwrap.c
88
+)
89
+
90
+# external dependency include directories
91
+target_include_directories(gadget PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include)
92
+target_include_directories(gadget PUBLIC ${CMAKE_CURRENT_BINARY_DIR}/fatfs)
93
+target_include_directories(gadget PUBLIC ${CMAKE_CURRENT_LIST_DIR}/st7789/src)
94
+target_include_directories(gadget PUBLIC ${CMAKE_CURRENT_LIST_DIR}/st7789/interface)
95
+target_include_directories(gadget PUBLIC ${CMAKE_CURRENT_LIST_DIR}/mcufont/decoder)
96
+target_include_directories(gadget PUBLIC ${CMAKE_CURRENT_LIST_DIR}/mcufont/fonts)
97
+target_include_directories(gadget PUBLIC ${CMAKE_CURRENT_LIST_DIR}/build)
98
+
99
+# compress source code and stuff we want to include
100
+add_custom_target(pack bash -c "./pack_data.sh"
101
+    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
102
+    BYPRODUCTS "build/pack_data.h"
103
+)
104
+add_dependencies(gadget pack)
105
+
106
+# enable generous warnings
107
+target_compile_options(gadget PUBLIC
108
+    -Wall
109
+    -Wextra
110
+    -Werror
111
+)
112
+
113
+# suppress some warnings for borked 3rd party files in Pico SDK
114
+set_source_files_properties(pico-sdk/lib/btstack/src/ble/sm.c PROPERTIES COMPILE_FLAGS -Wno-unused-parameter)
115
+set_source_files_properties(pico-sdk/lib/btstack/src/btstack_hid_parser.c PROPERTIES COMPILE_FLAGS -Wno-maybe-uninitialized)
116
+set_source_files_properties(pico-sdk/src/rp2_common/pico_cyw43_driver/cyw43_driver.c PROPERTIES COMPILE_FLAGS -Wno-unused-parameter)
117
+set_source_files_properties(pico-sdk/lib/btstack/src/classic/avdtp_util.c PROPERTIES COMPILE_FLAGS -Wno-unused-parameter)
118
+set_source_files_properties(pico-sdk/lib/btstack/src/classic/goep_client.c PROPERTIES COMPILE_FLAGS -Wno-unused-parameter)
119
+set_source_files_properties(pico-sdk/lib/btstack/src/classic/goep_server.c PROPERTIES COMPILE_FLAGS -Wno-unused-parameter)
120
+
121
+# pull in common dependencies
122
+target_link_libraries(gadget
123
+    pico_stdlib
124
+    pico_unique_id
125
+    tinyusb_device
126
+    tinyusb_board
127
+    hardware_spi
128
+    hardware_adc
129
+    hardware_gpio
130
+    hardware_pwm
131
+    pico_btstack_ble
132
+    pico_btstack_cyw43
133
+    pico_cyw43_arch_threadsafe_background
134
+)
135
+
136
+target_compile_definitions(gadget PUBLIC
137
+    RUNNING_AS_CLIENT=1
138
+    CYW43_LWIP=0
139
+)
140
+
141
+# fix for Errata RP2040-E5 (the fix requires use of GPIO 15)
142
+target_compile_definitions(gadget PUBLIC PICO_RP2040_USB_DEVICE_ENUMERATION_FIX=1)
143
+
144
+# create map/bin/hex/uf2 file etc.
145
+pico_add_extra_outputs(gadget)

+ 674
- 0
COPYING Bestand weergeven

@@ -0,0 +1,674 @@
1
+                    GNU GENERAL PUBLIC LICENSE
2
+                       Version 3, 29 June 2007
3
+
4
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
5
+ Everyone is permitted to copy and distribute verbatim copies
6
+ of this license document, but changing it is not allowed.
7
+
8
+                            Preamble
9
+
10
+  The GNU General Public License is a free, copyleft license for
11
+software and other kinds of works.
12
+
13
+  The licenses for most software and other practical works are designed
14
+to take away your freedom to share and change the works.  By contrast,
15
+the GNU General Public License is intended to guarantee your freedom to
16
+share and change all versions of a program--to make sure it remains free
17
+software for all its users.  We, the Free Software Foundation, use the
18
+GNU General Public License for most of our software; it applies also to
19
+any other work released this way by its authors.  You can apply it to
20
+your programs, too.
21
+
22
+  When we speak of free software, we are referring to freedom, not
23
+price.  Our General Public Licenses are designed to make sure that you
24
+have the freedom to distribute copies of free software (and charge for
25
+them if you wish), that you receive source code or can get it if you
26
+want it, that you can change the software or use pieces of it in new
27
+free programs, and that you know you can do these things.
28
+
29
+  To protect your rights, we need to prevent others from denying you
30
+these rights or asking you to surrender the rights.  Therefore, you have
31
+certain responsibilities if you distribute copies of the software, or if
32
+you modify it: responsibilities to respect the freedom of others.
33
+
34
+  For example, if you distribute copies of such a program, whether
35
+gratis or for a fee, you must pass on to the recipients the same
36
+freedoms that you received.  You must make sure that they, too, receive
37
+or can get the source code.  And you must show them these terms so they
38
+know their rights.
39
+
40
+  Developers that use the GNU GPL protect your rights with two steps:
41
+(1) assert copyright on the software, and (2) offer you this License
42
+giving you legal permission to copy, distribute and/or modify it.
43
+
44
+  For the developers' and authors' protection, the GPL clearly explains
45
+that there is no warranty for this free software.  For both users' and
46
+authors' sake, the GPL requires that modified versions be marked as
47
+changed, so that their problems will not be attributed erroneously to
48
+authors of previous versions.
49
+
50
+  Some devices are designed to deny users access to install or run
51
+modified versions of the software inside them, although the manufacturer
52
+can do so.  This is fundamentally incompatible with the aim of
53
+protecting users' freedom to change the software.  The systematic
54
+pattern of such abuse occurs in the area of products for individuals to
55
+use, which is precisely where it is most unacceptable.  Therefore, we
56
+have designed this version of the GPL to prohibit the practice for those
57
+products.  If such problems arise substantially in other domains, we
58
+stand ready to extend this provision to those domains in future versions
59
+of the GPL, as needed to protect the freedom of users.
60
+
61
+  Finally, every program is threatened constantly by software patents.
62
+States should not allow patents to restrict development and use of
63
+software on general-purpose computers, but in those that do, we wish to
64
+avoid the special danger that patents applied to a free program could
65
+make it effectively proprietary.  To prevent this, the GPL assures that
66
+patents cannot be used to render the program non-free.
67
+
68
+  The precise terms and conditions for copying, distribution and
69
+modification follow.
70
+
71
+                       TERMS AND CONDITIONS
72
+
73
+  0. Definitions.
74
+
75
+  "This License" refers to version 3 of the GNU General Public License.
76
+
77
+  "Copyright" also means copyright-like laws that apply to other kinds of
78
+works, such as semiconductor masks.
79
+
80
+  "The Program" refers to any copyrightable work licensed under this
81
+License.  Each licensee is addressed as "you".  "Licensees" and
82
+"recipients" may be individuals or organizations.
83
+
84
+  To "modify" a work means to copy from or adapt all or part of the work
85
+in a fashion requiring copyright permission, other than the making of an
86
+exact copy.  The resulting work is called a "modified version" of the
87
+earlier work or a work "based on" the earlier work.
88
+
89
+  A "covered work" means either the unmodified Program or a work based
90
+on the Program.
91
+
92
+  To "propagate" a work means to do anything with it that, without
93
+permission, would make you directly or secondarily liable for
94
+infringement under applicable copyright law, except executing it on a
95
+computer or modifying a private copy.  Propagation includes copying,
96
+distribution (with or without modification), making available to the
97
+public, and in some countries other activities as well.
98
+
99
+  To "convey" a work means any kind of propagation that enables other
100
+parties to make or receive copies.  Mere interaction with a user through
101
+a computer network, with no transfer of a copy, is not conveying.
102
+
103
+  An interactive user interface displays "Appropriate Legal Notices"
104
+to the extent that it includes a convenient and prominently visible
105
+feature that (1) displays an appropriate copyright notice, and (2)
106
+tells the user that there is no warranty for the work (except to the
107
+extent that warranties are provided), that licensees may convey the
108
+work under this License, and how to view a copy of this License.  If
109
+the interface presents a list of user commands or options, such as a
110
+menu, a prominent item in the list meets this criterion.
111
+
112
+  1. Source Code.
113
+
114
+  The "source code" for a work means the preferred form of the work
115
+for making modifications to it.  "Object code" means any non-source
116
+form of a work.
117
+
118
+  A "Standard Interface" means an interface that either is an official
119
+standard defined by a recognized standards body, or, in the case of
120
+interfaces specified for a particular programming language, one that
121
+is widely used among developers working in that language.
122
+
123
+  The "System Libraries" of an executable work include anything, other
124
+than the work as a whole, that (a) is included in the normal form of
125
+packaging a Major Component, but which is not part of that Major
126
+Component, and (b) serves only to enable use of the work with that
127
+Major Component, or to implement a Standard Interface for which an
128
+implementation is available to the public in source code form.  A
129
+"Major Component", in this context, means a major essential component
130
+(kernel, window system, and so on) of the specific operating system
131
+(if any) on which the executable work runs, or a compiler used to
132
+produce the work, or an object code interpreter used to run it.
133
+
134
+  The "Corresponding Source" for a work in object code form means all
135
+the source code needed to generate, install, and (for an executable
136
+work) run the object code and to modify the work, including scripts to
137
+control those activities.  However, it does not include the work's
138
+System Libraries, or general-purpose tools or generally available free
139
+programs which are used unmodified in performing those activities but
140
+which are not part of the work.  For example, Corresponding Source
141
+includes interface definition files associated with source files for
142
+the work, and the source code for shared libraries and dynamically
143
+linked subprograms that the work is specifically designed to require,
144
+such as by intimate data communication or control flow between those
145
+subprograms and other parts of the work.
146
+
147
+  The Corresponding Source need not include anything that users
148
+can regenerate automatically from other parts of the Corresponding
149
+Source.
150
+
151
+  The Corresponding Source for a work in source code form is that
152
+same work.
153
+
154
+  2. Basic Permissions.
155
+
156
+  All rights granted under this License are granted for the term of
157
+copyright on the Program, and are irrevocable provided the stated
158
+conditions are met.  This License explicitly affirms your unlimited
159
+permission to run the unmodified Program.  The output from running a
160
+covered work is covered by this License only if the output, given its
161
+content, constitutes a covered work.  This License acknowledges your
162
+rights of fair use or other equivalent, as provided by copyright law.
163
+
164
+  You may make, run and propagate covered works that you do not
165
+convey, without conditions so long as your license otherwise remains
166
+in force.  You may convey covered works to others for the sole purpose
167
+of having them make modifications exclusively for you, or provide you
168
+with facilities for running those works, provided that you comply with
169
+the terms of this License in conveying all material for which you do
170
+not control copyright.  Those thus making or running the covered works
171
+for you must do so exclusively on your behalf, under your direction
172
+and control, on terms that prohibit them from making any copies of
173
+your copyrighted material outside their relationship with you.
174
+
175
+  Conveying under any other circumstances is permitted solely under
176
+the conditions stated below.  Sublicensing is not allowed; section 10
177
+makes it unnecessary.
178
+
179
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180
+
181
+  No covered work shall be deemed part of an effective technological
182
+measure under any applicable law fulfilling obligations under article
183
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
184
+similar laws prohibiting or restricting circumvention of such
185
+measures.
186
+
187
+  When you convey a covered work, you waive any legal power to forbid
188
+circumvention of technological measures to the extent such circumvention
189
+is effected by exercising rights under this License with respect to
190
+the covered work, and you disclaim any intention to limit operation or
191
+modification of the work as a means of enforcing, against the work's
192
+users, your or third parties' legal rights to forbid circumvention of
193
+technological measures.
194
+
195
+  4. Conveying Verbatim Copies.
196
+
197
+  You may convey verbatim copies of the Program's source code as you
198
+receive it, in any medium, provided that you conspicuously and
199
+appropriately publish on each copy an appropriate copyright notice;
200
+keep intact all notices stating that this License and any
201
+non-permissive terms added in accord with section 7 apply to the code;
202
+keep intact all notices of the absence of any warranty; and give all
203
+recipients a copy of this License along with the Program.
204
+
205
+  You may charge any price or no price for each copy that you convey,
206
+and you may offer support or warranty protection for a fee.
207
+
208
+  5. Conveying Modified Source Versions.
209
+
210
+  You may convey a work based on the Program, or the modifications to
211
+produce it from the Program, in the form of source code under the
212
+terms of section 4, provided that you also meet all of these conditions:
213
+
214
+    a) The work must carry prominent notices stating that you modified
215
+    it, and giving a relevant date.
216
+
217
+    b) The work must carry prominent notices stating that it is
218
+    released under this License and any conditions added under section
219
+    7.  This requirement modifies the requirement in section 4 to
220
+    "keep intact all notices".
221
+
222
+    c) You must license the entire work, as a whole, under this
223
+    License to anyone who comes into possession of a copy.  This
224
+    License will therefore apply, along with any applicable section 7
225
+    additional terms, to the whole of the work, and all its parts,
226
+    regardless of how they are packaged.  This License gives no
227
+    permission to license the work in any other way, but it does not
228
+    invalidate such permission if you have separately received it.
229
+
230
+    d) If the work has interactive user interfaces, each must display
231
+    Appropriate Legal Notices; however, if the Program has interactive
232
+    interfaces that do not display Appropriate Legal Notices, your
233
+    work need not make them do so.
234
+
235
+  A compilation of a covered work with other separate and independent
236
+works, which are not by their nature extensions of the covered work,
237
+and which are not combined with it such as to form a larger program,
238
+in or on a volume of a storage or distribution medium, is called an
239
+"aggregate" if the compilation and its resulting copyright are not
240
+used to limit the access or legal rights of the compilation's users
241
+beyond what the individual works permit.  Inclusion of a covered work
242
+in an aggregate does not cause this License to apply to the other
243
+parts of the aggregate.
244
+
245
+  6. Conveying Non-Source Forms.
246
+
247
+  You may convey a covered work in object code form under the terms
248
+of sections 4 and 5, provided that you also convey the
249
+machine-readable Corresponding Source under the terms of this License,
250
+in one of these ways:
251
+
252
+    a) Convey the object code in, or embodied in, a physical product
253
+    (including a physical distribution medium), accompanied by the
254
+    Corresponding Source fixed on a durable physical medium
255
+    customarily used for software interchange.
256
+
257
+    b) Convey the object code in, or embodied in, a physical product
258
+    (including a physical distribution medium), accompanied by a
259
+    written offer, valid for at least three years and valid for as
260
+    long as you offer spare parts or customer support for that product
261
+    model, to give anyone who possesses the object code either (1) a
262
+    copy of the Corresponding Source for all the software in the
263
+    product that is covered by this License, on a durable physical
264
+    medium customarily used for software interchange, for a price no
265
+    more than your reasonable cost of physically performing this
266
+    conveying of source, or (2) access to copy the
267
+    Corresponding Source from a network server at no charge.
268
+
269
+    c) Convey individual copies of the object code with a copy of the
270
+    written offer to provide the Corresponding Source.  This
271
+    alternative is allowed only occasionally and noncommercially, and
272
+    only if you received the object code with such an offer, in accord
273
+    with subsection 6b.
274
+
275
+    d) Convey the object code by offering access from a designated
276
+    place (gratis or for a charge), and offer equivalent access to the
277
+    Corresponding Source in the same way through the same place at no
278
+    further charge.  You need not require recipients to copy the
279
+    Corresponding Source along with the object code.  If the place to
280
+    copy the object code is a network server, the Corresponding Source
281
+    may be on a different server (operated by you or a third party)
282
+    that supports equivalent copying facilities, provided you maintain
283
+    clear directions next to the object code saying where to find the
284
+    Corresponding Source.  Regardless of what server hosts the
285
+    Corresponding Source, you remain obligated to ensure that it is
286
+    available for as long as needed to satisfy these requirements.
287
+
288
+    e) Convey the object code using peer-to-peer transmission, provided
289
+    you inform other peers where the object code and Corresponding
290
+    Source of the work are being offered to the general public at no
291
+    charge under subsection 6d.
292
+
293
+  A separable portion of the object code, whose source code is excluded
294
+from the Corresponding Source as a System Library, need not be
295
+included in conveying the object code work.
296
+
297
+  A "User Product" is either (1) a "consumer product", which means any
298
+tangible personal property which is normally used for personal, family,
299
+or household purposes, or (2) anything designed or sold for incorporation
300
+into a dwelling.  In determining whether a product is a consumer product,
301
+doubtful cases shall be resolved in favor of coverage.  For a particular
302
+product received by a particular user, "normally used" refers to a
303
+typical or common use of that class of product, regardless of the status
304
+of the particular user or of the way in which the particular user
305
+actually uses, or expects or is expected to use, the product.  A product
306
+is a consumer product regardless of whether the product has substantial
307
+commercial, industrial or non-consumer uses, unless such uses represent
308
+the only significant mode of use of the product.
309
+
310
+  "Installation Information" for a User Product means any methods,
311
+procedures, authorization keys, or other information required to install
312
+and execute modified versions of a covered work in that User Product from
313
+a modified version of its Corresponding Source.  The information must
314
+suffice to ensure that the continued functioning of the modified object
315
+code is in no case prevented or interfered with solely because
316
+modification has been made.
317
+
318
+  If you convey an object code work under this section in, or with, or
319
+specifically for use in, a User Product, and the conveying occurs as
320
+part of a transaction in which the right of possession and use of the
321
+User Product is transferred to the recipient in perpetuity or for a
322
+fixed term (regardless of how the transaction is characterized), the
323
+Corresponding Source conveyed under this section must be accompanied
324
+by the Installation Information.  But this requirement does not apply
325
+if neither you nor any third party retains the ability to install
326
+modified object code on the User Product (for example, the work has
327
+been installed in ROM).
328
+
329
+  The requirement to provide Installation Information does not include a
330
+requirement to continue to provide support service, warranty, or updates
331
+for a work that has been modified or installed by the recipient, or for
332
+the User Product in which it has been modified or installed.  Access to a
333
+network may be denied when the modification itself materially and
334
+adversely affects the operation of the network or violates the rules and
335
+protocols for communication across the network.
336
+
337
+  Corresponding Source conveyed, and Installation Information provided,
338
+in accord with this section must be in a format that is publicly
339
+documented (and with an implementation available to the public in
340
+source code form), and must require no special password or key for
341
+unpacking, reading or copying.
342
+
343
+  7. Additional Terms.
344
+
345
+  "Additional permissions" are terms that supplement the terms of this
346
+License by making exceptions from one or more of its conditions.
347
+Additional permissions that are applicable to the entire Program shall
348
+be treated as though they were included in this License, to the extent
349
+that they are valid under applicable law.  If additional permissions
350
+apply only to part of the Program, that part may be used separately
351
+under those permissions, but the entire Program remains governed by
352
+this License without regard to the additional permissions.
353
+
354
+  When you convey a copy of a covered work, you may at your option
355
+remove any additional permissions from that copy, or from any part of
356
+it.  (Additional permissions may be written to require their own
357
+removal in certain cases when you modify the work.)  You may place
358
+additional permissions on material, added by you to a covered work,
359
+for which you have or can give appropriate copyright permission.
360
+
361
+  Notwithstanding any other provision of this License, for material you
362
+add to a covered work, you may (if authorized by the copyright holders of
363
+that material) supplement the terms of this License with terms:
364
+
365
+    a) Disclaiming warranty or limiting liability differently from the
366
+    terms of sections 15 and 16 of this License; or
367
+
368
+    b) Requiring preservation of specified reasonable legal notices or
369
+    author attributions in that material or in the Appropriate Legal
370
+    Notices displayed by works containing it; or
371
+
372
+    c) Prohibiting misrepresentation of the origin of that material, or
373
+    requiring that modified versions of such material be marked in
374
+    reasonable ways as different from the original version; or
375
+
376
+    d) Limiting the use for publicity purposes of names of licensors or
377
+    authors of the material; or
378
+
379
+    e) Declining to grant rights under trademark law for use of some
380
+    trade names, trademarks, or service marks; or
381
+
382
+    f) Requiring indemnification of licensors and authors of that
383
+    material by anyone who conveys the material (or modified versions of
384
+    it) with contractual assumptions of liability to the recipient, for
385
+    any liability that these contractual assumptions directly impose on
386
+    those licensors and authors.
387
+
388
+  All other non-permissive additional terms are considered "further
389
+restrictions" within the meaning of section 10.  If the Program as you
390
+received it, or any part of it, contains a notice stating that it is
391
+governed by this License along with a term that is a further
392
+restriction, you may remove that term.  If a license document contains
393
+a further restriction but permits relicensing or conveying under this
394
+License, you may add to a covered work material governed by the terms
395
+of that license document, provided that the further restriction does
396
+not survive such relicensing or conveying.
397
+
398
+  If you add terms to a covered work in accord with this section, you
399
+must place, in the relevant source files, a statement of the
400
+additional terms that apply to those files, or a notice indicating
401
+where to find the applicable terms.
402
+
403
+  Additional terms, permissive or non-permissive, may be stated in the
404
+form of a separately written license, or stated as exceptions;
405
+the above requirements apply either way.
406
+
407
+  8. Termination.
408
+
409
+  You may not propagate or modify a covered work except as expressly
410
+provided under this License.  Any attempt otherwise to propagate or
411
+modify it is void, and will automatically terminate your rights under
412
+this License (including any patent licenses granted under the third
413
+paragraph of section 11).
414
+
415
+  However, if you cease all violation of this License, then your
416
+license from a particular copyright holder is reinstated (a)
417
+provisionally, unless and until the copyright holder explicitly and
418
+finally terminates your license, and (b) permanently, if the copyright
419
+holder fails to notify you of the violation by some reasonable means
420
+prior to 60 days after the cessation.
421
+
422
+  Moreover, your license from a particular copyright holder is
423
+reinstated permanently if the copyright holder notifies you of the
424
+violation by some reasonable means, this is the first time you have
425
+received notice of violation of this License (for any work) from that
426
+copyright holder, and you cure the violation prior to 30 days after
427
+your receipt of the notice.
428
+
429
+  Termination of your rights under this section does not terminate the
430
+licenses of parties who have received copies or rights from you under
431
+this License.  If your rights have been terminated and not permanently
432
+reinstated, you do not qualify to receive new licenses for the same
433
+material under section 10.
434
+
435
+  9. Acceptance Not Required for Having Copies.
436
+
437
+  You are not required to accept this License in order to receive or
438
+run a copy of the Program.  Ancillary propagation of a covered work
439
+occurring solely as a consequence of using peer-to-peer transmission
440
+to receive a copy likewise does not require acceptance.  However,
441
+nothing other than this License grants you permission to propagate or
442
+modify any covered work.  These actions infringe copyright if you do
443
+not accept this License.  Therefore, by modifying or propagating a
444
+covered work, you indicate your acceptance of this License to do so.
445
+
446
+  10. Automatic Licensing of Downstream Recipients.
447
+
448
+  Each time you convey a covered work, the recipient automatically
449
+receives a license from the original licensors, to run, modify and
450
+propagate that work, subject to this License.  You are not responsible
451
+for enforcing compliance by third parties with this License.
452
+
453
+  An "entity transaction" is a transaction transferring control of an
454
+organization, or substantially all assets of one, or subdividing an
455
+organization, or merging organizations.  If propagation of a covered
456
+work results from an entity transaction, each party to that
457
+transaction who receives a copy of the work also receives whatever
458
+licenses to the work the party's predecessor in interest had or could
459
+give under the previous paragraph, plus a right to possession of the
460
+Corresponding Source of the work from the predecessor in interest, if
461
+the predecessor has it or can get it with reasonable efforts.
462
+
463
+  You may not impose any further restrictions on the exercise of the
464
+rights granted or affirmed under this License.  For example, you may
465
+not impose a license fee, royalty, or other charge for exercise of
466
+rights granted under this License, and you may not initiate litigation
467
+(including a cross-claim or counterclaim in a lawsuit) alleging that
468
+any patent claim is infringed by making, using, selling, offering for
469
+sale, or importing the Program or any portion of it.
470
+
471
+  11. Patents.
472
+
473
+  A "contributor" is a copyright holder who authorizes use under this
474
+License of the Program or a work on which the Program is based.  The
475
+work thus licensed is called the contributor's "contributor version".
476
+
477
+  A contributor's "essential patent claims" are all patent claims
478
+owned or controlled by the contributor, whether already acquired or
479
+hereafter acquired, that would be infringed by some manner, permitted
480
+by this License, of making, using, or selling its contributor version,
481
+but do not include claims that would be infringed only as a
482
+consequence of further modification of the contributor version.  For
483
+purposes of this definition, "control" includes the right to grant
484
+patent sublicenses in a manner consistent with the requirements of
485
+this License.
486
+
487
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
488
+patent license under the contributor's essential patent claims, to
489
+make, use, sell, offer for sale, import and otherwise run, modify and
490
+propagate the contents of its contributor version.
491
+
492
+  In the following three paragraphs, a "patent license" is any express
493
+agreement or commitment, however denominated, not to enforce a patent
494
+(such as an express permission to practice a patent or covenant not to
495
+sue for patent infringement).  To "grant" such a patent license to a
496
+party means to make such an agreement or commitment not to enforce a
497
+patent against the party.
498
+
499
+  If you convey a covered work, knowingly relying on a patent license,
500
+and the Corresponding Source of the work is not available for anyone
501
+to copy, free of charge and under the terms of this License, through a
502
+publicly available network server or other readily accessible means,
503
+then you must either (1) cause the Corresponding Source to be so
504
+available, or (2) arrange to deprive yourself of the benefit of the
505
+patent license for this particular work, or (3) arrange, in a manner
506
+consistent with the requirements of this License, to extend the patent
507
+license to downstream recipients.  "Knowingly relying" means you have
508
+actual knowledge that, but for the patent license, your conveying the
509
+covered work in a country, or your recipient's use of the covered work
510
+in a country, would infringe one or more identifiable patents in that
511
+country that you have reason to believe are valid.
512
+
513
+  If, pursuant to or in connection with a single transaction or
514
+arrangement, you convey, or propagate by procuring conveyance of, a
515
+covered work, and grant a patent license to some of the parties
516
+receiving the covered work authorizing them to use, propagate, modify
517
+or convey a specific copy of the covered work, then the patent license
518
+you grant is automatically extended to all recipients of the covered
519
+work and works based on it.
520
+
521
+  A patent license is "discriminatory" if it does not include within
522
+the scope of its coverage, prohibits the exercise of, or is
523
+conditioned on the non-exercise of one or more of the rights that are
524
+specifically granted under this License.  You may not convey a covered
525
+work if you are a party to an arrangement with a third party that is
526
+in the business of distributing software, under which you make payment
527
+to the third party based on the extent of your activity of conveying
528
+the work, and under which the third party grants, to any of the
529
+parties who would receive the covered work from you, a discriminatory
530
+patent license (a) in connection with copies of the covered work
531
+conveyed by you (or copies made from those copies), or (b) primarily
532
+for and in connection with specific products or compilations that
533
+contain the covered work, unless you entered into that arrangement,
534
+or that patent license was granted, prior to 28 March 2007.
535
+
536
+  Nothing in this License shall be construed as excluding or limiting
537
+any implied license or other defenses to infringement that may
538
+otherwise be available to you under applicable patent law.
539
+
540
+  12. No Surrender of Others' Freedom.
541
+
542
+  If conditions are imposed on you (whether by court order, agreement or
543
+otherwise) that contradict the conditions of this License, they do not
544
+excuse you from the conditions of this License.  If you cannot convey a
545
+covered work so as to satisfy simultaneously your obligations under this
546
+License and any other pertinent obligations, then as a consequence you may
547
+not convey it at all.  For example, if you agree to terms that obligate you
548
+to collect a royalty for further conveying from those to whom you convey
549
+the Program, the only way you could satisfy both those terms and this
550
+License would be to refrain entirely from conveying the Program.
551
+
552
+  13. Use with the GNU Affero General Public License.
553
+
554
+  Notwithstanding any other provision of this License, you have
555
+permission to link or combine any covered work with a work licensed
556
+under version 3 of the GNU Affero General Public License into a single
557
+combined work, and to convey the resulting work.  The terms of this
558
+License will continue to apply to the part which is the covered work,
559
+but the special requirements of the GNU Affero General Public License,
560
+section 13, concerning interaction through a network will apply to the
561
+combination as such.
562
+
563
+  14. Revised Versions of this License.
564
+
565
+  The Free Software Foundation may publish revised and/or new versions of
566
+the GNU General Public License from time to time.  Such new versions will
567
+be similar in spirit to the present version, but may differ in detail to
568
+address new problems or concerns.
569
+
570
+  Each version is given a distinguishing version number.  If the
571
+Program specifies that a certain numbered version of the GNU General
572
+Public License "or any later version" applies to it, you have the
573
+option of following the terms and conditions either of that numbered
574
+version or of any later version published by the Free Software
575
+Foundation.  If the Program does not specify a version number of the
576
+GNU General Public License, you may choose any version ever published
577
+by the Free Software Foundation.
578
+
579
+  If the Program specifies that a proxy can decide which future
580
+versions of the GNU General Public License can be used, that proxy's
581
+public statement of acceptance of a version permanently authorizes you
582
+to choose that version for the Program.
583
+
584
+  Later license versions may give you additional or different
585
+permissions.  However, no additional obligations are imposed on any
586
+author or copyright holder as a result of your choosing to follow a
587
+later version.
588
+
589
+  15. Disclaimer of Warranty.
590
+
591
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599
+
600
+  16. Limitation of Liability.
601
+
602
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610
+SUCH DAMAGES.
611
+
612
+  17. Interpretation of Sections 15 and 16.
613
+
614
+  If the disclaimer of warranty and limitation of liability provided
615
+above cannot be given local legal effect according to their terms,
616
+reviewing courts shall apply local law that most closely approximates
617
+an absolute waiver of all civil liability in connection with the
618
+Program, unless a warranty or assumption of liability accompanies a
619
+copy of the Program in return for a fee.
620
+
621
+                     END OF TERMS AND CONDITIONS
622
+
623
+            How to Apply These Terms to Your New Programs
624
+
625
+  If you develop a new program, and you want it to be of the greatest
626
+possible use to the public, the best way to achieve this is to make it
627
+free software which everyone can redistribute and change under these terms.
628
+
629
+  To do so, attach the following notices to the program.  It is safest
630
+to attach them to the start of each source file to most effectively
631
+state the exclusion of warranty; and each file should have at least
632
+the "copyright" line and a pointer to where the full notice is found.
633
+
634
+    <one line to give the program's name and a brief idea of what it does.>
635
+    Copyright (C) <year>  <name of author>
636
+
637
+    This program is free software: you can redistribute it and/or modify
638
+    it under the terms of the GNU General Public License as published by
639
+    the Free Software Foundation, either version 3 of the License, or
640
+    (at your option) any later version.
641
+
642
+    This program is distributed in the hope that it will be useful,
643
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
644
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
645
+    GNU General Public License for more details.
646
+
647
+    You should have received a copy of the GNU General Public License
648
+    along with this program.  If not, see <https://www.gnu.org/licenses/>.
649
+
650
+Also add information on how to contact you by electronic and paper mail.
651
+
652
+  If the program does terminal interaction, make it output a short
653
+notice like this when it starts in an interactive mode:
654
+
655
+    <program>  Copyright (C) <year>  <name of author>
656
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657
+    This is free software, and you are welcome to redistribute it
658
+    under certain conditions; type `show c' for details.
659
+
660
+The hypothetical commands `show w' and `show c' should show the appropriate
661
+parts of the General Public License.  Of course, your program's commands
662
+might be different; for a GUI interface, you would use an "about box".
663
+
664
+  You should also get your employer (if you work as a programmer) or school,
665
+if any, to sign a "copyright disclaimer" for the program, if necessary.
666
+For more information on this, and how to apply and follow the GNU GPL, see
667
+<https://www.gnu.org/licenses/>.
668
+
669
+  The GNU General Public License does not permit incorporating your program
670
+into proprietary programs.  If your program is a subroutine library, you
671
+may consider it more useful to permit linking proprietary applications with
672
+the library.  If this is what you want to do, use the GNU Lesser General
673
+Public License instead of this License.  But first, please read
674
+<https://www.gnu.org/licenses/why-not-lgpl.html>.

+ 131
- 0
README.md Bestand weergeven

@@ -0,0 +1,131 @@
1
+# Pi Pico Volcano Remote Control Gadget
2
+
3
+For use with Raspberry Pi Pico W boards with the [Waveshare Pico LCD 1.3](https://www.waveshare.com/wiki/Pico-LCD-1.3) and the [Pimoroni Pico Lipo Shim](https://shop.pimoroni.com/products/pico-lipo-shim).
4
+
5
+Adapted from the [tinyusb-cdc-example](https://github.com/hathach/tinyusb/blob/master/examples/device/cdc_msc/src/main.c), [adc example](https://github.com/raspberrypi/pico-examples/tree/master/adc/read_vsys), [standalone client example](https://github.com/raspberrypi/pico-examples/blob/master/pico_w/bt/standalone/client.c) and my [Trackball firmware](https://git.xythobuz.de/thomas/Trackball).
6
+
7
+## Quick Start
8
+
9
+When compiling for the first time, check out the required git submodules.
10
+
11
+    git submodule update --init
12
+    cd pico-sdk
13
+    git submodule update --init
14
+
15
+Then do this to build.
16
+
17
+    mkdir build
18
+    cd build
19
+    cmake -DPICO_BOARD=pico_w ..
20
+    make -j4 gadget
21
+
22
+And flash the resulting `gadget.uf2` file to your Pico as usual.
23
+
24
+For convenience you can use the included `flash.sh`, as long as you flashed the binary manually once before.
25
+
26
+    make -j4 gadget
27
+    ../flash.sh gadget.uf2
28
+
29
+This will use the mass storage bootloader to upload a new uf2 image.
30
+
31
+For old-school debugging a serial port will be presented by the firmware.
32
+Open it using eg. `picocom`, or with the included `debug.sh` script.
33
+
34
+For dependencies to compile, on Arch install these.
35
+
36
+    sudo pacman -S arm-none-eabi-gcc arm-none-eabi-newlib picocom cmake
37
+
38
+## Proper Debugging
39
+
40
+You can also use the SWD interface for proper hardware debugging.
41
+
42
+This follows the instructions from the [RP2040 Getting Started document](https://datasheets.raspberrypi.com/pico/getting-started-with-pico.pdf) from chapter 5 and 6.
43
+
44
+For ease of reading the disassembly, create a debug build.
45
+
46
+    mkdir build_debug
47
+    cd build_debug
48
+    cmake -DPICO_BOARD=pico_w -DCMAKE_BUILD_TYPE=Debug ..
49
+    make -j4 gadget
50
+
51
+You need a hardware SWD probe.
52
+This can be made from another Pico, see Appendix A in the document linked above.
53
+For this you need to compile the `picoprobe` firmware, like this.
54
+
55
+    git clone https://github.com/raspberrypi/picoprobe.git
56
+    cd picoprobe
57
+
58
+    git submodule update --init
59
+    mkdir build
60
+    cd build
61
+
62
+    PICO_SDK_PATH=../../../pico-sdk cmake ..
63
+    make -j4
64
+
65
+    cd ../.. # back to build_debug directory from before
66
+
67
+And flash the resulting `picoprobe.uf2` to your probe.
68
+Connect `GP2` of the probe to `SWCLK` of the target and `GP3` of the probe to `SWDIO` of the target.
69
+Of course you also need to connect GND between both.
70
+
71
+You need some dependencies, mainly `gdb-multiarch` and the RP2040 fork of `OpenOCD`.
72
+
73
+    sudo apt install gdb-multiarch   # Debian / Ubuntu
74
+    sudo pacman -S arm-none-eabi-gdb # Arch Linux
75
+
76
+    git clone https://github.com/raspberrypi/openocd.git --branch rp2040 --recursive --depth=1
77
+    cd openocd
78
+
79
+    # install udev rules
80
+    sudo cp contrib/60-openocd.rules /etc/udev/rules.d
81
+    sudo udevadm control --reload-rules && sudo udevadm trigger
82
+
83
+    ./bootstrap
84
+    ./configure --enable-ftdi --enable-sysfsgpio --enable-bcm2835gpio
85
+    make -j4
86
+
87
+    cd .. # back to build_debug directory from before
88
+
89
+Now we can flash a firmware image via OpenOCD.
90
+
91
+    ./openocd/src/openocd -s openocd/tcl -f interface/cmsis-dap.cfg -f target/rp2040.cfg -c "adapter speed 5000" -c "cmsis_dap_vid_pid 0x2e8a 0x000c" -c "program gadget.elf verify reset exit"
92
+
93
+And also start a GDB debugging session.
94
+
95
+    ./openocd/src/openocd -s openocd/tcl -f interface/cmsis-dap.cfg -f target/rp2040.cfg -c "adapter speed 5000" -c "cmsis_dap_vid_pid 0x2e8a 0x000c"
96
+    arm-none-eabi-gdb gadget.elf
97
+    target extended-remote localhost:3333
98
+
99
+    load # program elf into flash
100
+    monitor reset init # put into clean initial state
101
+    continue # start program
102
+
103
+These commands have also been put in the `flash_swd.sh` and `debug_swd.sh` scripts, respectively.
104
+They require the `build_debug` folder where you checked out and built OpenOCD.
105
+Here are some [general GDB tips](https://beej.us/guide/bggdb/).
106
+
107
+## License
108
+
109
+The firmware itself is licensed as GPLv3.
110
+I initially adapted it from my own [Trackball](https://git.xythobuz.de/thomas/Trackball) project.
111
+It uses the [Pi Pico SDK](https://github.com/raspberrypi/pico-sdk), licensed as BSD 3-clause, and therefore also [TinyUSB](https://github.com/hathach/tinyusb), licensed under the MIT license.
112
+Some code is adapted from the TinyUSB examples.
113
+And the project uses the [FatFS library](https://github.com/abbrev/fatfs), licensed as BSD 1-clause.
114
+Also included are the [MCUFont library](https://github.com/mcufont/mcufont) and the [st7789 library](https://github.com/hepingood/st7789), both licensed under the MIT license.
115
+It also uses the [BTstack](https://github.com/bluekitchen/btstack/blob/master/LICENSE) included with the Pico SDK, following their [license terms](https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/pico_btstack/LICENSE.RP).
116
+
117
+The case design is also licensed as GPLv3.
118
+It uses a [Pi Pico case model](https://www.printables.com/model/210898-raspberry-pi-pico-case) licensed as CC-BY-NC-SA.
119
+But this is only used for visualization purposes and doesn't influence the 3D model at all.
120
+
121
+    This program is free software: you can redistribute it and/or modify
122
+    it under the terms of the GNU General Public License as published by
123
+    the Free Software Foundation, either version 3 of the License, or
124
+    (at your option) any later version.
125
+
126
+    This program is distributed in the hope that it will be useful,
127
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
128
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
129
+    GNU General Public License for more details.
130
+
131
+    See <http://www.gnu.org/licenses/>.

+ 33
- 0
case/case.scad Bestand weergeven

@@ -0,0 +1,33 @@
1
+// https://www.waveshare.com/wiki/Pico-LCD-1.3
2
+lcd_w = 52.0;
3
+lcd_h = 26.5;
4
+
5
+lcd_d = 1.0; // todo
6
+
7
+// https://www.printables.com/model/210898-raspberry-pi-pico-case
8
+use <pico_case.scad>
9
+p_w = 21;
10
+p_h = 51;
11
+p_t = 1.0;
12
+
13
+pico_header_h = 2.0; // todo
14
+lcd_header_h = 10.0; // todo
15
+
16
+header_h = pico_header_h + lcd_header_h;
17
+
18
+module lcd() {
19
+    color("blue")
20
+    translate([-lcd_h / 2, -lcd_w / 2, 0])
21
+    cube([lcd_h, lcd_w, lcd_d]);
22
+}
23
+
24
+module hw() {
25
+    translate([0, 0, header_h])
26
+    lcd();
27
+    
28
+    translate([p_w / 2, -p_h / 2, 0])
29
+    rotate([0, 180, 0])
30
+    pico();
31
+}
32
+
33
+hw();

+ 245
- 0
case/pico_case.scad Bestand weergeven

@@ -0,0 +1,245 @@
1
+resolution = 20; //[10, 20, 30, 50, 100]
2
+$fn = resolution;
3
+
4
+show_frame = false; // [true, false]
5
+show_pico = true; // [true, false]
6
+show_lid = false; // [true, false]
7
+show_button = false; // [true, false]
8
+
9
+module rounded_plate(w, h, t, d) {
10
+    translate([0, (d/2), 0])
11
+    cube([w, (h-d), t]);
12
+    translate([(d/2), 0, 0])
13
+    cube([(w-d), h, t]);
14
+    translate([(d/2), (d/2), 0])
15
+    cylinder(h=t, d=d);
16
+    translate([w-(d/2), (d/2), 0])
17
+    cylinder(h=t, d=d);
18
+    translate([(d/2), h-(d/2), 0])
19
+    cylinder(h=t, d=d);
20
+    translate([w-(d/2), h-(d/2), 0])
21
+    cylinder(h=t, d=d);
22
+}
23
+
24
+module usb_port() {
25
+    translate([0.3, 0, 0])
26
+    color("grey")
27
+    difference() {
28
+        union() {
29
+            cube([7.4, 6, 2.7]);
30
+            translate([-0.3, 5.4, -0.1])
31
+            cube([8, 0.6, 2.9]);
32
+        }
33
+    }
34
+}
35
+
36
+module switch() {
37
+    color("grey")
38
+    cube([3.5, 4.5, 1]);
39
+    color("white")
40
+    translate([(3.5/2), (4.5/2), 0])
41
+    cylinder(h=1.5, d=3);
42
+}
43
+
44
+p_w = 21;
45
+p_h = 51;
46
+p_t = 1.0;
47
+os = 0.35;
48
+b = 2;
49
+
50
+module pico() {
51
+    translate([((p_w-8)/2), ((p_h+1.3)-6), p_t])
52
+    usb_port();
53
+
54
+    translate([5.25, 36.6, 0.6])
55
+    switch();
56
+
57
+    difference() {
58
+        color("green")
59
+        cube([p_w, p_h, 1]);
60
+
61
+        translate([((p_w/2)-5.7), 2, -0.01])
62
+        cylinder(h=p_t+0.02, d=2.1);
63
+       
64
+        translate([(p_w-((p_w/2)-5.7)), 2, -0.01])
65
+        cylinder(h=p_t+0.02, d=2.1);
66
+
67
+        translate([((p_w/2)-5.7), (p_h-2), -0.01])
68
+        cylinder(h=p_t+0.02, d=2.1);
69
+
70
+        translate([(p_w-((p_w/2)-5.7)), (p_h-2), -0.01])
71
+        cylinder(h=p_t+0.02, d=2.1);
72
+    }
73
+}
74
+
75
+if (show_pico) {
76
+    translate([(os+b), (os+b), 3.4])
77
+    pico();
78
+}
79
+
80
+if (show_frame) {
81
+    union() {
82
+        difference() {
83
+            // Outer frame
84
+            rounded_plate(p_w+(2*(os+b)), p_h+(2*(os+b)), 10, (2*(os+b)));
85
+            
86
+            // Board cut-out
87
+            translate([b, b, -1])
88
+            cube([p_w+(2*os), p_h+(2*os), 12]);
89
+            
90
+            // USB port cut-out
91
+//            translate([b+os+((p_w-10)/2), b+os+((p_h+1)-6), 3.4])
92
+//            cube([10, 8, 4.7]);
93
+            translate([b+os+((p_w-10)/2), b+(2*os)+p_h-1, 8.1])
94
+            rotate([-90, 0, 0])
95
+            rounded_plate(10, 4.7, 4.7, 2);
96
+            
97
+            // Lid release cut-out
98
+            difference() {
99
+                union() {
100
+                    translate([b+os+((p_w-10)/2), -1, 10-1])
101
+                    cube([10, 8, 2]);
102
+                    translate([b+os+((p_w-6)/2), -1, 10-1])
103
+                    rotate([-90, 0, 0])
104
+                    cylinder(h=4, d=2); 
105
+                    translate([b+os+((p_w-6)/2), -1, 10-2])
106
+                    cube([6, 8, 3]);
107
+                    translate([b+os+((p_w+6)/2), -1, 10-1])
108
+                    rotate([-90, 0, 0])
109
+                    cylinder(h=4, d=2); 
110
+                }
111
+                translate([b+os+((p_w+10)/2), -1, 10-1])
112
+                rotate([-90, 0, 0])
113
+                cylinder(h=4, d=2); 
114
+                translate([b+os+((p_w-10)/2), -1, 10-1])
115
+                rotate([-90, 0, 0])
116
+                cylinder(h=4, d=2); 
117
+            }
118
+            
119
+            // Outer frame
120
+            translate([1, 1, 10-1.2])
121
+            rounded_plate(p_w+(2*(os+b))-2, p_h+(2*(os+b))-2, 2, (2*(os+b))-2);
122
+
123
+            // Left top retainer cut-out
124
+            translate([b+os+(p_w/2)-(3.2/2)-7.1, b+p_h+(2*os), 10-3+0.2])
125
+            cube([3.2, 0.7, 1.0]);
126
+
127
+            // Right top retainer cut-out
128
+            translate([b+os+(p_w/2)-(3.2/2)+7.1, b+p_h+(2*os), 10-3+0.2])
129
+            cube([3.2, 0.7, 1.0]);
130
+
131
+            // Left bottom retainer cut-out
132
+            translate([b+os+(p_w/2)-(4/2)-7.1, b+os-0.7, 10-3+0.2])
133
+            cube([4, 0.7, 1.0]);
134
+
135
+            // Right bottom retainer cut-out
136
+            translate([b+os+(p_w/2)-(4/2)+7.1, b+os-0.7, 10-3+0.2])
137
+            cube([4, 0.7, 1.0]);
138
+
139
+            // Left retainer cut-out
140
+            translate([b-0.7, b+os+(p_h/2)-2, 10-3+0.2])
141
+            cube([0.7, 4, 1.0]);
142
+
143
+            // Right retainer cut-out
144
+            translate([b+(2*os)+p_w, b+os+(p_h/2)-2, 10-3+0.2])
145
+            cube([0.7, 4, 1.0]);
146
+        }
147
+        
148
+        // Left support rail
149
+        translate([b+os+(p_w/2)-(3/2)-5.5, 0, 0])
150
+        cube([3, p_h+(2*(os+b)), 3.4]);
151
+       
152
+        // Right support rail
153
+        translate([b+os+(p_w/2)-(3/2)+5.5, 0, 0])
154
+        cube([3, p_h+(2*(os+b)), 3.4]);
155
+
156
+        // Left top retainer
157
+        translate([b+os+(p_w/2)-(2.2/2)-6.1, b+p_h+(2*os)-0.6, 3.4+1+0.5])
158
+        cube([2.2, 0.6, 0.8]);
159
+
160
+        // Right top retainer
161
+        translate([b+os+(p_w/2)-(2.2/2)+6.1, b+p_h+(2*os)-0.6, 3.4+1+0.5])
162
+        cube([2.2, 0.6, 0.8]);
163
+
164
+        // Left bottom retainer
165
+        translate([b+os+(p_w/2)-(3/2)-5.7, b, 3.4+1+0.])
166
+        cube([3, 0.6, 0.8]);
167
+
168
+        // Right bottom retainer
169
+        translate([b+os+(p_w/2)-(3/2)+5.7, b, 3.4+1+0.5])
170
+        cube([3, 0.6, 0.8]);
171
+    }
172
+}
173
+
174
+if (show_lid) {
175
+    difference() {
176
+        union() {
177
+            translate([1+os, 1+os, 10-1.2])
178
+            rounded_plate(p_w+(2*b)-2, p_h+(2*b)-2, 1, (2*b)-2);
179
+            translate([(os+b), (os+b), 10-3])
180
+            rounded_plate(p_w, p_h, 2, (2*b)-3);
181
+            
182
+            // Left top retainer
183
+            translate([b+os+(p_w/2)-(2.2/2)-7.1, b+p_h+os, 10-3+0.4])
184
+            cube([2.2, 0.6, 0.6]);
185
+
186
+            // Right top retainer
187
+            translate([b+os+(p_w/2)-(2.2/2)+7.1, b+p_h+os, 10-3+0.4])
188
+            cube([2.2, 0.6, 0.6]);
189
+
190
+            // Left bottom retainer
191
+            translate([b+os+(p_w/2)-(3/2)-7.1, b+os-0.5, 10-3+0.3])
192
+            cube([3, 0.6, 0.6]);
193
+
194
+            // Right bottom retainer
195
+            translate([b+os+(p_w/2)-(3/2)+7.1, b+os-0.5, 10-3+0.3])
196
+            cube([3, 0.6, 0.6]);
197
+
198
+            // Left retainer
199
+            translate([b+os-0.6, b+os+(p_h/2)-1.5, 10-3+0.3])
200
+            cube([0.6, 3, 0.6]);            
201
+
202
+            // Right retainer
203
+            translate([b+os+p_w, b+os+(p_h/2)-1.5, 10-3+0.3])
204
+            cube([0.6, 3, 0.6]);            
205
+        }
206
+
207
+        // USB port cut-out
208
+        translate([b+os+((p_w-10)/2), b+p_h+(2*os)-2, 10-4.2])
209
+        cube([10, 8, 2.4]);
210
+
211
+        // Lid release cut-out
212
+        translate([b+os+((p_w-10)/2), -1, 10-4.2])
213
+        cube([10, 8, 3]);
214
+        
215
+        // Switch cut-out
216
+        translate([(os+b)+7, (os+b)+38.85, 0])
217
+        cylinder(h=10, d=5);
218
+        
219
+        // Inset
220
+        translate([(os+b)+1, (os+b)+1, 10-4])
221
+        cube([p_w-2, p_h-2, 3]);
222
+    }
223
+    // Switch surround
224
+    translate([(os+b), (os+b)+38.85-6, 10-2])
225
+    cube([p_w, 1, 1]);    
226
+
227
+    translate([(os+b), (os+b)+38.85+5, 10-2])
228
+    cube([p_w, 1, 1]);    
229
+
230
+}
231
+
232
+
233
+if (show_button) {
234
+    color("red")
235
+    union() {
236
+        translate([(os+b)+7, (os+b)+38.85, 7.5])
237
+        cylinder(h=2.3, d=4.4);
238
+        difference() {
239
+            translate([(os+b)+7, (os+b)+38.85, 6.5])
240
+            cylinder(h=2, d=7.6);
241
+            translate([(os+b)+7, (os+b)+38.85, 6.5])
242
+            cylinder(h=1, d=6.6);
243
+        }
244
+    }
245
+}

+ 18
- 0
debug.sh Bestand weergeven

@@ -0,0 +1,18 @@
1
+#!/bin/bash
2
+set -euo pipefail
3
+
4
+SERIAL=/dev/serial/by-id/usb-xythobuz_VolcanoRC_*
5
+
6
+echo -n Waiting for serial port to appear
7
+until [ -e $SERIAL ]
8
+do
9
+    echo -n .
10
+    sleep 1
11
+done
12
+echo
13
+
14
+echo Opening picocom terminal
15
+echo "[C-a] [C-x] to exit"
16
+echo
17
+
18
+picocom -q --omap crcrlf --imap lfcrlf $SERIAL

+ 31
- 0
debug_swd.sh Bestand weergeven

@@ -0,0 +1,31 @@
1
+#!/bin/bash
2
+set -euo pipefail
3
+
4
+cd "$(dirname "$0")"
5
+
6
+echo Starting OpenOCD in background
7
+echo "\n\nstarting new openocd" >> openocd.log
8
+./build_debug/openocd/src/openocd -s build_debug/openocd/tcl -f interface/cmsis-dap.cfg -f target/rp2040.cfg -c "adapter speed 5000" -c "cmsis_dap_vid_pid 0x2e8a 0x000c" >> openocd.log 2>&1 &
9
+OPENOCD_PID=$!
10
+
11
+# give OpenOCD some time to output stuff
12
+sleep 1
13
+
14
+echo -n Waiting for debugger to appear
15
+while ! netstat -tna | grep 'LISTEN\>' | grep -q ':3333\>'; do
16
+    echo -n .
17
+    sleep 1
18
+done
19
+
20
+echo Starting GDB
21
+arm-none-eabi-gdb \
22
+-ex "set history save" \
23
+-ex "show print pretty" \
24
+-ex "target extended-remote localhost:3333" \
25
+-ex "tui new-layout default src 1 status 1 cmd 2" \
26
+-ex "tui layout default" \
27
+-ex "tui enable" \
28
+$1
29
+
30
+echo Killing OpenOCD instance in background
31
+kill $OPENOCD_PID

+ 1
- 0
fatfs

@@ -0,0 +1 @@
1
+Subproject commit b11f08931929e5f2f1fe8a3a2c0bd16d222b5625

+ 28
- 0
flash.sh Bestand weergeven

@@ -0,0 +1,28 @@
1
+#!/bin/bash
2
+set -euo pipefail
3
+
4
+SERIAL=/dev/serial/by-id/usb-xythobuz_VolcanoRC_*
5
+DISK=/dev/disk/by-label/RPI-RP2
6
+MOUNT=/mnt/tmp
7
+
8
+if [ ! -e $DISK ]
9
+then
10
+    echo Resetting Raspberry Pi Pico
11
+    echo -n -e "\\x18" > $SERIAL
12
+fi
13
+
14
+echo -n Waiting for disk to appear
15
+until [ -e $DISK ]
16
+do
17
+    echo -n .
18
+    sleep 1
19
+done
20
+echo
21
+
22
+echo Mounting bootloader disk
23
+sudo mount $DISK $MOUNT
24
+
25
+echo Copying binary
26
+sudo cp $1 $MOUNT
27
+
28
+echo Done

+ 6
- 0
flash_swd.sh Bestand weergeven

@@ -0,0 +1,6 @@
1
+#!/bin/bash
2
+set -euo pipefail
3
+
4
+cd "$(dirname "$0")"
5
+
6
+./build_debug/openocd/src/openocd -s build_debug/openocd/tcl -f interface/cmsis-dap.cfg -f target/rp2040.cfg -c "adapter speed 5000" -c "cmsis_dap_vid_pid 0x2e8a 0x000c" -c "program $1 verify reset exit"

+ 61
- 0
include/ble.h Bestand weergeven

@@ -0,0 +1,61 @@
1
+/*
2
+ * ble.h
3
+ *
4
+ * Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#ifndef __BLE_H__
20
+#define __BLE_H__
21
+
22
+#include "btstack.h"
23
+
24
+#define BLE_MAX_NAME_LENGTH 32
25
+#define BLE_MAX_DATA_LENGTH 26
26
+#define BLE_MAX_SCAN_RESULTS 32
27
+#define BLE_MAX_VALUE_LEN 64
28
+
29
+enum ble_scan_mode {
30
+    BLE_SCAN_OFF    = 0,
31
+    BLE_SCAN_ON     = 1,
32
+    BLE_SCAN_TOGGLE = 2,
33
+};
34
+
35
+struct ble_scan_result {
36
+    bool set;
37
+    uint32_t time;
38
+
39
+    bd_addr_t addr;
40
+    bd_addr_type_t type;
41
+    int8_t rssi;
42
+    char name[BLE_MAX_NAME_LENGTH + 1];
43
+    uint8_t data[BLE_MAX_DATA_LENGTH];
44
+    size_t data_len;
45
+};
46
+
47
+void ble_init(void);
48
+bool ble_is_ready(void);
49
+
50
+void ble_scan(enum ble_scan_mode mode);
51
+int32_t ble_get_scan_results(struct ble_scan_result *buf, uint16_t len);
52
+
53
+void ble_connect(bd_addr_t addr, bd_addr_type_t type);
54
+bool ble_is_connected(void);
55
+void ble_disconnect(void);
56
+
57
+int32_t ble_read(const uint8_t *characteristic, uint8_t *buff, uint16_t buff_len);
58
+int8_t ble_write(const uint8_t *service, const uint8_t *characteristic,
59
+                 uint8_t *buff, uint16_t buff_len);
60
+
61
+#endif // __BLE_H__

+ 58
- 0
include/btstack_config.h Bestand weergeven

@@ -0,0 +1,58 @@
1
+#ifndef _PICO_BTSTACK_BTSTACK_CONFIG_H
2
+#define _PICO_BTSTACK_BTSTACK_CONFIG_H
3
+
4
+#ifndef ENABLE_BLE
5
+#error Please link to pico_btstack_ble
6
+#endif
7
+
8
+// BTstack features that can be enabled
9
+#define ENABLE_LE_PERIPHERAL
10
+#define ENABLE_LOG_INFO
11
+#define ENABLE_LOG_ERROR
12
+#define ENABLE_PRINTF_HEXDUMP
13
+
14
+// for the client
15
+#if RUNNING_AS_CLIENT
16
+#define ENABLE_LE_CENTRAL
17
+#define MAX_NR_GATT_CLIENTS 1
18
+#else
19
+#define MAX_NR_GATT_CLIENTS 0
20
+#endif
21
+
22
+// BTstack configuration. buffers, sizes, ...
23
+#define HCI_OUTGOING_PRE_BUFFER_SIZE 4
24
+#define HCI_ACL_PAYLOAD_SIZE (255 + 4)
25
+#define HCI_ACL_CHUNK_SIZE_ALIGNMENT 4
26
+#define MAX_NR_HCI_CONNECTIONS 1
27
+#define MAX_NR_SM_LOOKUP_ENTRIES 3
28
+#define MAX_NR_WHITELIST_ENTRIES 16
29
+#define MAX_NR_LE_DEVICE_DB_ENTRIES 16
30
+
31
+// Limit number of ACL/SCO Buffer to use by stack to avoid cyw43 shared bus overrun
32
+#define MAX_NR_CONTROLLER_ACL_BUFFERS 3
33
+#define MAX_NR_CONTROLLER_SCO_PACKETS 3
34
+
35
+// Enable and configure HCI Controller to Host Flow Control to avoid cyw43 shared bus overrun
36
+#define ENABLE_HCI_CONTROLLER_TO_HOST_FLOW_CONTROL
37
+#define HCI_HOST_ACL_PACKET_LEN (255+4)
38
+#define HCI_HOST_ACL_PACKET_NUM 3
39
+#define HCI_HOST_SCO_PACKET_LEN 120
40
+#define HCI_HOST_SCO_PACKET_NUM 3
41
+
42
+// Link Key DB and LE Device DB using TLV on top of Flash Sector interface
43
+#define NVM_NUM_DEVICE_DB_ENTRIES 16
44
+#define NVM_NUM_LINK_KEYS 16
45
+
46
+// We don't give btstack a malloc, so use a fixed-size ATT DB.
47
+#define MAX_ATT_DB_SIZE 512
48
+
49
+// BTstack HAL configuration
50
+#define HAVE_EMBEDDED_TIME_MS
51
+// map btstack_assert onto Pico SDK assert()
52
+#define HAVE_ASSERT
53
+// Some USB dongles take longer to respond to HCI reset (e.g. BCM20702A).
54
+#define HCI_RESET_RESEND_TIMEOUT_MS 1000
55
+#define ENABLE_SOFTWARE_AES128
56
+#define ENABLE_MICRO_ECC_FOR_LE_SECURE_CONNECTIONS
57
+
58
+#endif // _PICO_BTSTACK_BTSTACK_CONFIG_H

+ 42
- 0
include/buttons.h Bestand weergeven

@@ -0,0 +1,42 @@
1
+/*
2
+ * buttons.h
3
+ *
4
+ * Copyright (c) 2022 - 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#ifndef __BUTTONS_H__
20
+#define __BUTTONS_H__
21
+
22
+#include <stdbool.h>
23
+
24
+enum buttons {
25
+    BTN_A = 0,
26
+    BTN_B,
27
+    BTN_X,
28
+    BTN_Y,
29
+    BTN_UP,
30
+    BTN_DOWN,
31
+    BTN_LEFT,
32
+    BTN_RIGHT,
33
+    BTN_ENTER,
34
+    NUM_BTNS // count
35
+};
36
+
37
+void buttons_init(void);
38
+void buttons_callback(void (*fp)(enum buttons, bool));
39
+void buttons_run(void);
40
+
41
+#endif // __BUTTONS_H__
42
+

+ 48
- 0
include/config.h Bestand weergeven

@@ -0,0 +1,48 @@
1
+/*
2
+ * config.h
3
+ *
4
+ * Copyright (c) 2022 - 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#ifndef __CONFIG_H__
20
+#define __CONFIG_H__
21
+
22
+#define MENU_PREFER_VOLCANO
23
+//#define MENU_PREFER_CRAFTY
24
+
25
+#define WATCHDOG_PERIOD_MS 1000
26
+
27
+// ASCII 0x18 = CAN (cancel)
28
+#define ENTER_BOOTLOADER_MAGIC 0x18
29
+
30
+//#define DISABLE_CDC_DTR_CHECK
31
+#define DEBOUNCE_DELAY_MS 5
32
+
33
+#define SERIAL_WRITES_BLOCK_WHEN_BUFFER_FULL
34
+
35
+#define DEBUG_DISK_WRITE_SOURCES
36
+
37
+#define DISK_BLOCK_SIZE 512
38
+
39
+#ifdef DEBUG_DISK_WRITE_SOURCES
40
+#define DISK_BLOCK_COUNT (256 + 128)
41
+#else // DEBUG_DISK_WRITE_SOURCES
42
+#define DISK_BLOCK_COUNT 256
43
+#endif // DEBUG_DISK_WRITE_SOURCES
44
+
45
+//#define TEST_VOLCANO_AUTO_CONNECT "xx:xx:xx:xx:xx:xx 1"
46
+//#define TEST_CRAFTY_AUTO_CONNECT "xx:xx:xx:xx:xx:xx 0"
47
+
48
+#endif // __CONFIG_H__

+ 26
- 0
include/console.h Bestand weergeven

@@ -0,0 +1,26 @@
1
+/*
2
+ * console.h
3
+ *
4
+ * Copyright (c) 2022 - 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#ifndef __CONSOLE_H__
20
+#define __CONSOLE_H__
21
+
22
+void cnsl_init(void);
23
+void cnsl_run(void);
24
+void cnsl_handle_input(const uint8_t *buf, size_t len);
25
+
26
+#endif // __CONSOLE_H__

+ 38
- 0
include/crafty.h Bestand weergeven

@@ -0,0 +1,38 @@
1
+/*
2
+ * crafty.h
3
+ *
4
+ * Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#ifndef __CRAFTY_H__
20
+#define __CRAFTY_H__
21
+
22
+#include <stdint.h>
23
+#include <stdbool.h>
24
+
25
+// in 1/10th degrees C, or < 0 on error
26
+int16_t crafty_get_current_temp(void);
27
+int16_t crafty_get_target_temp(void);
28
+
29
+// v in 1/10th degrees C, returns < 0 on error
30
+int8_t crafty_set_target_temp(uint16_t v);
31
+
32
+// returns < 0 on error
33
+int8_t crafty_set_heater_state(bool value);
34
+
35
+// in percent, or < 0 on error
36
+int8_t crafty_get_battery_state(void);
37
+
38
+#endif // __CRAFTY_H__

+ 27
- 0
include/debug_disk.h Bestand weergeven

@@ -0,0 +1,27 @@
1
+/*
2
+ * debug_disk.h
3
+ *
4
+ * Copyright (c) 2022 - 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#ifndef __DEBUG_DISK_H__
20
+#define __DEBUG_DISK_H__
21
+
22
+void debug_disk_init(void);
23
+
24
+int debug_disk_mount(void);
25
+int debug_disk_unmount(void);
26
+
27
+#endif // __DEBUG_DISK_H__

+ 21
- 0
include/fat_disk.h Bestand weergeven

@@ -0,0 +1,21 @@
1
+/* 
2
+ * fat_disk.h
3
+ *
4
+ * Copyright (c) 2022 - 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+void fat_disk_init(void);
20
+
21
+uint8_t *fat_disk_get_sector(uint32_t sector);

+ 296
- 0
include/ffconf.h Bestand weergeven

@@ -0,0 +1,296 @@
1
+/*---------------------------------------------------------------------------/
2
+/  Configurations of FatFs Module
3
+/---------------------------------------------------------------------------*/
4
+
5
+#define FFCONF_DEF	80286	/* Revision ID */
6
+
7
+/*---------------------------------------------------------------------------/
8
+/ Function Configurations
9
+/---------------------------------------------------------------------------*/
10
+
11
+#define FF_FS_READONLY	0
12
+/* This option switches read-only configuration. (0:Read/Write or 1:Read-only)
13
+/  Read-only configuration removes writing API functions, f_write(), f_sync(),
14
+/  f_unlink(), f_mkdir(), f_chmod(), f_rename(), f_truncate(), f_getfree()
15
+/  and optional writing functions as well. */
16
+
17
+
18
+#define FF_FS_MINIMIZE	0
19
+/* This option defines minimization level to remove some basic API functions.
20
+/
21
+/   0: Basic functions are fully enabled.
22
+/   1: f_stat(), f_getfree(), f_unlink(), f_mkdir(), f_truncate() and f_rename()
23
+/      are removed.
24
+/   2: f_opendir(), f_readdir() and f_closedir() are removed in addition to 1.
25
+/   3: f_lseek() function is removed in addition to 2. */
26
+
27
+
28
+#define FF_USE_FIND		0
29
+/* This option switches filtered directory read functions, f_findfirst() and
30
+/  f_findnext(). (0:Disable, 1:Enable 2:Enable with matching altname[] too) */
31
+
32
+
33
+#define FF_USE_MKFS		1
34
+/* This option switches f_mkfs() function. (0:Disable or 1:Enable) */
35
+
36
+
37
+#define FF_USE_FASTSEEK	0
38
+/* This option switches fast seek function. (0:Disable or 1:Enable) */
39
+
40
+
41
+#define FF_USE_EXPAND	0
42
+/* This option switches f_expand function. (0:Disable or 1:Enable) */
43
+
44
+
45
+#define FF_USE_CHMOD	0
46
+/* This option switches attribute manipulation functions, f_chmod() and f_utime().
47
+/  (0:Disable or 1:Enable) Also FF_FS_READONLY needs to be 0 to enable this option. */
48
+
49
+
50
+#define FF_USE_LABEL	1
51
+/* This option switches volume label functions, f_getlabel() and f_setlabel().
52
+/  (0:Disable or 1:Enable) */
53
+
54
+
55
+#define FF_USE_FORWARD	0
56
+/* This option switches f_forward() function. (0:Disable or 1:Enable) */
57
+
58
+
59
+#define FF_USE_STRFUNC	0
60
+#define FF_PRINT_LLI	1
61
+#define FF_PRINT_FLOAT	1
62
+#define FF_STRF_ENCODE	3
63
+/* FF_USE_STRFUNC switches string functions, f_gets(), f_putc(), f_puts() and
64
+/  f_printf().
65
+/
66
+/   0: Disable. FF_PRINT_LLI, FF_PRINT_FLOAT and FF_STRF_ENCODE have no effect.
67
+/   1: Enable without LF-CRLF conversion.
68
+/   2: Enable with LF-CRLF conversion.
69
+/
70
+/  FF_PRINT_LLI = 1 makes f_printf() support long long argument and FF_PRINT_FLOAT = 1/2
71
+/  makes f_printf() support floating point argument. These features want C99 or later.
72
+/  When FF_LFN_UNICODE >= 1 with LFN enabled, string functions convert the character
73
+/  encoding in it. FF_STRF_ENCODE selects assumption of character encoding ON THE FILE
74
+/  to be read/written via those functions.
75
+/
76
+/   0: ANSI/OEM in current CP
77
+/   1: Unicode in UTF-16LE
78
+/   2: Unicode in UTF-16BE
79
+/   3: Unicode in UTF-8
80
+*/
81
+
82
+
83
+/*---------------------------------------------------------------------------/
84
+/ Locale and Namespace Configurations
85
+/---------------------------------------------------------------------------*/
86
+
87
+#define FF_CODE_PAGE	850
88
+/* This option specifies the OEM code page to be used on the target system.
89
+/  Incorrect code page setting can cause a file open failure.
90
+/
91
+/   437 - U.S.
92
+/   720 - Arabic
93
+/   737 - Greek
94
+/   771 - KBL
95
+/   775 - Baltic
96
+/   850 - Latin 1
97
+/   852 - Latin 2
98
+/   855 - Cyrillic
99
+/   857 - Turkish
100
+/   860 - Portuguese
101
+/   861 - Icelandic
102
+/   862 - Hebrew
103
+/   863 - Canadian French
104
+/   864 - Arabic
105
+/   865 - Nordic
106
+/   866 - Russian
107
+/   869 - Greek 2
108
+/   932 - Japanese (DBCS)
109
+/   936 - Simplified Chinese (DBCS)
110
+/   949 - Korean (DBCS)
111
+/   950 - Traditional Chinese (DBCS)
112
+/     0 - Include all code pages above and configured by f_setcp()
113
+*/
114
+
115
+
116
+#define FF_USE_LFN		3
117
+#define FF_MAX_LFN		255
118
+/* The FF_USE_LFN switches the support for LFN (long file name).
119
+/
120
+/   0: Disable LFN. FF_MAX_LFN has no effect.
121
+/   1: Enable LFN with static  working buffer on the BSS. Always NOT thread-safe.
122
+/   2: Enable LFN with dynamic working buffer on the STACK.
123
+/   3: Enable LFN with dynamic working buffer on the HEAP.
124
+/
125
+/  To enable the LFN, ffunicode.c needs to be added to the project. The LFN function
126
+/  requiers certain internal working buffer occupies (FF_MAX_LFN + 1) * 2 bytes and
127
+/  additional (FF_MAX_LFN + 44) / 15 * 32 bytes when exFAT is enabled.
128
+/  The FF_MAX_LFN defines size of the working buffer in UTF-16 code unit and it can
129
+/  be in range of 12 to 255. It is recommended to be set it 255 to fully support LFN
130
+/  specification.
131
+/  When use stack for the working buffer, take care on stack overflow. When use heap
132
+/  memory for the working buffer, memory management functions, ff_memalloc() and
133
+/  ff_memfree() exemplified in ffsystem.c, need to be added to the project. */
134
+
135
+
136
+#define FF_LFN_UNICODE	2
137
+/* This option switches the character encoding on the API when LFN is enabled.
138
+/
139
+/   0: ANSI/OEM in current CP (TCHAR = char)
140
+/   1: Unicode in UTF-16 (TCHAR = WCHAR)
141
+/   2: Unicode in UTF-8 (TCHAR = char)
142
+/   3: Unicode in UTF-32 (TCHAR = DWORD)
143
+/
144
+/  Also behavior of string I/O functions will be affected by this option.
145
+/  When LFN is not enabled, this option has no effect. */
146
+
147
+
148
+#define FF_LFN_BUF		255
149
+#define FF_SFN_BUF		12
150
+/* This set of options defines size of file name members in the FILINFO structure
151
+/  which is used to read out directory items. These values should be suffcient for
152
+/  the file names to read. The maximum possible length of the read file name depends
153
+/  on character encoding. When LFN is not enabled, these options have no effect. */
154
+
155
+
156
+#define FF_FS_RPATH		0
157
+/* This option configures support for relative path.
158
+/
159
+/   0: Disable relative path and remove related functions.
160
+/   1: Enable relative path. f_chdir() and f_chdrive() are available.
161
+/   2: f_getcwd() function is available in addition to 1.
162
+*/
163
+
164
+
165
+/*---------------------------------------------------------------------------/
166
+/ Drive/Volume Configurations
167
+/---------------------------------------------------------------------------*/
168
+
169
+#define FF_VOLUMES		1
170
+/* Number of volumes (logical drives) to be used. (1-10) */
171
+
172
+
173
+#define FF_STR_VOLUME_ID	0
174
+#define FF_VOLUME_STRS		"RAM","NAND","CF","SD","SD2","USB","USB2","USB3"
175
+/* FF_STR_VOLUME_ID switches support for volume ID in arbitrary strings.
176
+/  When FF_STR_VOLUME_ID is set to 1 or 2, arbitrary strings can be used as drive
177
+/  number in the path name. FF_VOLUME_STRS defines the volume ID strings for each
178
+/  logical drives. Number of items must not be less than FF_VOLUMES. Valid
179
+/  characters for the volume ID strings are A-Z, a-z and 0-9, however, they are
180
+/  compared in case-insensitive. If FF_STR_VOLUME_ID >= 1 and FF_VOLUME_STRS is
181
+/  not defined, a user defined volume string table is needed as:
182
+/
183
+/  const char* VolumeStr[FF_VOLUMES] = {"ram","flash","sd","usb",...
184
+*/
185
+
186
+
187
+#define FF_MULTI_PARTITION	0
188
+/* This option switches support for multiple volumes on the physical drive.
189
+/  By default (0), each logical drive number is bound to the same physical drive
190
+/  number and only an FAT volume found on the physical drive will be mounted.
191
+/  When this function is enabled (1), each logical drive number can be bound to
192
+/  arbitrary physical drive and partition listed in the VolToPart[]. Also f_fdisk()
193
+/  function will be available. */
194
+
195
+
196
+#define FF_MIN_SS		512
197
+#define FF_MAX_SS		512
198
+/* This set of options configures the range of sector size to be supported. (512,
199
+/  1024, 2048 or 4096) Always set both 512 for most systems, generic memory card and
200
+/  harddisk, but a larger value may be required for on-board flash memory and some
201
+/  type of optical media. When FF_MAX_SS is larger than FF_MIN_SS, FatFs is configured
202
+/  for variable sector size mode and disk_ioctl() function needs to implement
203
+/  GET_SECTOR_SIZE command. */
204
+
205
+
206
+#define FF_LBA64		0
207
+/* This option switches support for 64-bit LBA. (0:Disable or 1:Enable)
208
+/  To enable the 64-bit LBA, also exFAT needs to be enabled. (FF_FS_EXFAT == 1) */
209
+
210
+
211
+#define FF_MIN_GPT		0x10000000
212
+/* Minimum number of sectors to switch GPT as partitioning format in f_mkfs and
213
+/  f_fdisk function. 0x100000000 max. This option has no effect when FF_LBA64 == 0. */
214
+
215
+
216
+#define FF_USE_TRIM		0
217
+/* This option switches support for ATA-TRIM. (0:Disable or 1:Enable)
218
+/  To enable Trim function, also CTRL_TRIM command should be implemented to the
219
+/  disk_ioctl() function. */
220
+
221
+
222
+
223
+/*---------------------------------------------------------------------------/
224
+/ System Configurations
225
+/---------------------------------------------------------------------------*/
226
+
227
+#define FF_FS_TINY		0
228
+/* This option switches tiny buffer configuration. (0:Normal or 1:Tiny)
229
+/  At the tiny configuration, size of file object (FIL) is shrinked FF_MAX_SS bytes.
230
+/  Instead of private sector buffer eliminated from the file object, common sector
231
+/  buffer in the filesystem object (FATFS) is used for the file data transfer. */
232
+
233
+
234
+#define FF_FS_EXFAT		0
235
+/* This option switches support for exFAT filesystem. (0:Disable or 1:Enable)
236
+/  To enable exFAT, also LFN needs to be enabled. (FF_USE_LFN >= 1)
237
+/  Note that enabling exFAT discards ANSI C (C89) compatibility. */
238
+
239
+
240
+#define FF_FS_NORTC		1
241
+#define FF_NORTC_MON	1
242
+#define FF_NORTC_MDAY	1
243
+#define FF_NORTC_YEAR	2022
244
+/* The option FF_FS_NORTC switches timestamp feature. If the system does not have
245
+/  an RTC or valid timestamp is not needed, set FF_FS_NORTC = 1 to disable the
246
+/  timestamp feature. Every object modified by FatFs will have a fixed timestamp
247
+/  defined by FF_NORTC_MON, FF_NORTC_MDAY and FF_NORTC_YEAR in local time.
248
+/  To enable timestamp function (FF_FS_NORTC = 0), get_fattime() function need to be
249
+/  added to the project to read current time form real-time clock. FF_NORTC_MON,
250
+/  FF_NORTC_MDAY and FF_NORTC_YEAR have no effect.
251
+/  These options have no effect in read-only configuration (FF_FS_READONLY = 1). */
252
+
253
+
254
+#define FF_FS_NOFSINFO	0
255
+/* If you need to know correct free space on the FAT32 volume, set bit 0 of this
256
+/  option, and f_getfree() function at the first time after volume mount will force
257
+/  a full FAT scan. Bit 1 controls the use of last allocated cluster number.
258
+/
259
+/  bit0=0: Use free cluster count in the FSINFO if available.
260
+/  bit0=1: Do not trust free cluster count in the FSINFO.
261
+/  bit1=0: Use last allocated cluster number in the FSINFO if available.
262
+/  bit1=1: Do not trust last allocated cluster number in the FSINFO.
263
+*/
264
+
265
+
266
+#define FF_FS_LOCK		0
267
+/* The option FF_FS_LOCK switches file lock function to control duplicated file open
268
+/  and illegal operation to open objects. This option must be 0 when FF_FS_READONLY
269
+/  is 1.
270
+/
271
+/  0:  Disable file lock function. To avoid volume corruption, application program
272
+/      should avoid illegal open, remove and rename to the open objects.
273
+/  >0: Enable file lock function. The value defines how many files/sub-directories
274
+/      can be opened simultaneously under file lock control. Note that the file
275
+/      lock control is independent of re-entrancy. */
276
+
277
+
278
+#define FF_FS_REENTRANT	0
279
+#define FF_FS_TIMEOUT	1000
280
+/* The option FF_FS_REENTRANT switches the re-entrancy (thread safe) of the FatFs
281
+/  module itself. Note that regardless of this option, file access to different
282
+/  volume is always re-entrant and volume control functions, f_mount(), f_mkfs()
283
+/  and f_fdisk() function, are always not re-entrant. Only file/directory access
284
+/  to the same volume is under control of this featuer.
285
+/
286
+/   0: Disable re-entrancy. FF_FS_TIMEOUT have no effect.
287
+/   1: Enable re-entrancy. Also user provided synchronization handlers,
288
+/      ff_mutex_create(), ff_mutex_delete(), ff_mutex_take() and ff_mutex_give()
289
+/      function, must be added to the project. Samples are available in ffsystem.c.
290
+/
291
+/  The FF_FS_TIMEOUT defines timeout period in unit of O/S time tick.
292
+*/
293
+
294
+
295
+
296
+/*--- End of configuration options ---*/

+ 30
- 0
include/image.h Bestand weergeven

@@ -0,0 +1,30 @@
1
+/*
2
+ * image.h
3
+ *
4
+ * Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#ifndef __IMAGE_H__
20
+#define __IMAGE_H__
21
+
22
+#include "pico/stdlib.h"
23
+
24
+void image_draw(const char *data, uint width, uint height);
25
+
26
+void draw_splash(void);
27
+void draw_battery_indicator(void);
28
+void battery_run(void);
29
+
30
+#endif // __IMAGE_H__

+ 44
- 0
include/lcd.h Bestand weergeven

@@ -0,0 +1,44 @@
1
+/*
2
+ * lipo.h
3
+ *
4
+ * Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#ifndef __LCD_H__
20
+#define __LCD_H__
21
+
22
+#include <stdint.h>
23
+
24
+#define RGB_565(r, g, b) ( \
25
+      (((r) >> 3) << 11)   \
26
+    | (((g) >> 2) << 5)    \
27
+    |  ((b) >> 3)          \
28
+)
29
+#define RGB_565_REV(c)            \
30
+    ((c & 0xF800) >> 8) / 255.0f, \
31
+    ((c & 0x07E0) >> 3) / 255.0f, \
32
+    ((c & 0x001F) << 3) / 255.0f
33
+uint32_t from_hsv(float h, float s, float v);
34
+
35
+void lcd_init(void);
36
+
37
+uint16_t lcd_get_backlight(void);
38
+void lcd_set_backlight(uint16_t value);
39
+
40
+void lcd_clear(void);
41
+void lcd_write_point(uint16_t x, uint16_t y, uint32_t color);
42
+void lcd_write_rect(uint16_t left, uint16_t top, uint16_t right, uint16_t bottom, uint32_t color);
43
+
44
+#endif // __LCD_H__

+ 26
- 0
include/lipo.h Bestand weergeven

@@ -0,0 +1,26 @@
1
+/*
2
+ * lipo.h
3
+ *
4
+ * Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#ifndef __LIPO_H__
20
+#define __LIPO_H__
21
+
22
+bool lipo_charging(void);
23
+float lipo_voltage(void);
24
+float lipo_percentage(float voltage);
25
+
26
+#endif // __LIPO_H__

+ 48
- 0
include/log.h Bestand weergeven

@@ -0,0 +1,48 @@
1
+/*
2
+ * log.h
3
+ *
4
+ * Copyright (c) 2022 - 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#ifndef __LOG_H__
20
+#define __LOG_H__
21
+
22
+#include <stdarg.h>
23
+#include <stdbool.h>
24
+#include "pico/stdlib.h"
25
+
26
+// for output that is stored in the debug log.
27
+// will be re-played from buffer when terminal connects
28
+#define debug(fmt, ...) debug_log(true, \
29
+        "%08lu %s: " fmt "\r\n", \
30
+        to_ms_since_boot(get_absolute_time()), \
31
+        __func__, \
32
+        ##__VA_ARGS__)
33
+
34
+// for interactive output. is not stored or re-played.
35
+#define print(fmt, ...) debug_log(false, fmt, ##__VA_ARGS__)
36
+#define println(fmt, ...) debug_log(false, fmt "\r\n", ##__VA_ARGS__)
37
+
38
+void debug_log(bool log, const char *format, ...) __attribute__((format(printf, 2, 3)));
39
+void debug_wait_input(const char *format, ...) __attribute__((format(printf, 1, 2)));
40
+void debug_log_va(bool log, const char *format, va_list args);
41
+
42
+void log_dump_to_usb(void);
43
+void log_dump_to_uart(void);
44
+void log_dump_to_disk(void);
45
+
46
+void debug_handle_input(const uint8_t *buff, size_t len);
47
+
48
+#endif // __LOG_H__

+ 3615
- 0
include/logo.h
Diff onderdrukt omdat het te groot bestand
Bestand weergeven


+ 24
- 0
include/main.h Bestand weergeven

@@ -0,0 +1,24 @@
1
+/*
2
+ * main.h
3
+ *
4
+ * Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#ifndef __MAIN_H__
20
+#define __MAIN_H__
21
+
22
+void main_loop_hw(void);
23
+
24
+#endif // __MAIN_H__

+ 37
- 0
include/menu.h Bestand weergeven

@@ -0,0 +1,37 @@
1
+/*
2
+ * menu.h
3
+ *
4
+ * Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#ifndef __MENU_H__
20
+#define __MENU_H__
21
+
22
+#define MENU_MAX_LINES 5
23
+#define MENU_MAX_LEN (MENU_MAX_LINES * 32)
24
+
25
+struct menu_state {
26
+    int off;
27
+    int selection;
28
+    int length;
29
+    char *buff;
30
+};
31
+
32
+void menu_init(void (*enter)(int), void (*exit)(void));
33
+void menu_deinit(void);
34
+
35
+void menu_run(void (*cb)(struct menu_state *));
36
+
37
+#endif // __MENU_H__

+ 33
- 0
include/models.h Bestand weergeven

@@ -0,0 +1,33 @@
1
+/*
2
+ * models.h
3
+ *
4
+ * Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#ifndef __MODELS_H__
20
+#define __MODELS_H__
21
+
22
+#include <stdint.h>
23
+
24
+enum known_devices {
25
+    DEV_UNKNOWN = 0,
26
+    DEV_VOLCANO,
27
+    DEV_CRAFTY,
28
+};
29
+
30
+enum known_devices models_filter_name(const char *name);
31
+int8_t models_get_serial(const uint8_t *data, size_t data_len, char *buff, size_t buff_len);
32
+
33
+#endif // __MODELS_H__

+ 43
- 0
include/ring.h Bestand weergeven

@@ -0,0 +1,43 @@
1
+/*
2
+ * ring.h
3
+ *
4
+ * Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#ifndef __RING_BUFFER_H__
20
+#define __RING_BUFFER_H__
21
+
22
+#include <stddef.h>
23
+#include <stdint.h>
24
+#include <stdbool.h>
25
+
26
+struct ring_buffer {
27
+    uint8_t *buffer;
28
+    size_t size;
29
+    size_t head, tail;
30
+    bool full;
31
+};
32
+#define RB_INIT(b, s) { .buffer = b, .size = s, .head = 0, .tail = 0, .full = false }
33
+
34
+void rb_add(struct ring_buffer *rb, const uint8_t *data, size_t length);
35
+#define rb_push(rb, v) rb_add(rb, &v, 1)
36
+size_t rb_len(struct ring_buffer *rb);
37
+#define rb_space(rb) ((rb)->size - rb_len(rb))
38
+void rb_dump(struct ring_buffer *rb, void (*write)(const uint8_t *, size_t));
39
+void rb_move(struct ring_buffer *rb, void (*write)(const uint8_t *, size_t));
40
+uint8_t rb_peek(struct ring_buffer *rb);
41
+uint8_t rb_pop(struct ring_buffer *rb);
42
+
43
+#endif // __RING_BUFFER_H__

+ 31
- 0
include/serial.h Bestand weergeven

@@ -0,0 +1,31 @@
1
+/*
2
+ * serial.h
3
+ *
4
+ * Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#ifndef __SERIAL_H__
20
+#define __SERIAL_H__
21
+
22
+#include <stddef.h>
23
+#include <stdint.h>
24
+#include <stdbool.h>
25
+
26
+void serial_init(void);
27
+void serial_write(const uint8_t *buf, size_t count);
28
+void serial_set_reroute(bool reroute);
29
+void serial_run(void);
30
+
31
+#endif // __SERIAL_H__

+ 33
- 0
include/state.h Bestand weergeven

@@ -0,0 +1,33 @@
1
+/*
2
+ * state.h
3
+ *
4
+ * Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#ifndef __STATE_H__
20
+#define __STATE_H__
21
+
22
+enum system_state {
23
+    STATE_INIT = 0,
24
+    STATE_SCAN,
25
+    STATE_VOLCANO_WORKFLOW,
26
+    STATE_VOLCANO_RUN,
27
+    STATE_CRAFTY,
28
+};
29
+
30
+void state_switch(enum system_state next);
31
+void state_run(void);
32
+
33
+#endif // __STATE_H__

+ 30
- 0
include/state_crafty.h Bestand weergeven

@@ -0,0 +1,30 @@
1
+/*
2
+ * state_crafty.h
3
+ *
4
+ * Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#ifndef __STATE_CRAFTY_H__
20
+#define __STATE_CRAFTY_H__
21
+
22
+#include <ble.h>
23
+
24
+void state_crafty_target(bd_addr_t addr, bd_addr_type_t type);
25
+
26
+void state_crafty_enter(void);
27
+void state_crafty_exit(void);
28
+void state_crafty_run(void);
29
+
30
+#endif // __STATE_CRAFTY_H__

+ 26
- 0
include/state_scan.h Bestand weergeven

@@ -0,0 +1,26 @@
1
+/*
2
+ * state_scan.h
3
+ *
4
+ * Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#ifndef __STATE_SCAN_H__
20
+#define __STATE_SCAN_H__
21
+
22
+void state_scan_enter(void);
23
+void state_scan_exit(void);
24
+void state_scan_run(void);
25
+
26
+#endif // __STATE_SCAN_H__

+ 31
- 0
include/state_volcano_run.h Bestand weergeven

@@ -0,0 +1,31 @@
1
+/*
2
+ * state_volcano_run.h
3
+ *
4
+ * Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#ifndef __STATE_VOLCANO_RUN_H__
20
+#define __STATE_VOLCANO_RUN_H__
21
+
22
+#include <ble.h>
23
+
24
+void state_volcano_run_index(uint16_t index);
25
+void state_volcano_run_target(bd_addr_t addr, bd_addr_type_t type);
26
+
27
+void state_volcano_run_enter(void);
28
+void state_volcano_run_exit(void);
29
+void state_volcano_run_run(void);
30
+
31
+#endif // __STATE_VOLCANO_RUN_H__

+ 26
- 0
include/state_volcano_workflow.h Bestand weergeven

@@ -0,0 +1,26 @@
1
+/*
2
+ * state_volcano_workflow.h
3
+ *
4
+ * Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#ifndef __STATE_VOLCANO_WORKFLOW_H__
20
+#define __STATE_VOLCANO_WORKFLOW_H__
21
+
22
+void state_volcano_wf_enter(void);
23
+void state_volcano_wf_exit(void);
24
+void state_volcano_wf_run(void);
25
+
26
+#endif // __STATE_VOLCANO_WORKFLOW_H__

+ 53
- 0
include/text.h Bestand weergeven

@@ -0,0 +1,53 @@
1
+/*
2
+ * text.h
3
+ *
4
+ * Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#ifndef __TEXT_H__
20
+#define __TEXT_H__
21
+
22
+#include "mcufont.h"
23
+
24
+#define TEXT_BG_NONE -1
25
+
26
+struct text_font {
27
+    const char *fontname;
28
+    //int scale; // TODO not supported - requires somewhere to store scaled versions
29
+
30
+    const struct mf_font_s *font;
31
+};
32
+
33
+struct text_conf {
34
+    const char *text;
35
+    int x;
36
+    int y;
37
+    bool justify;
38
+    enum mf_align_t alignment;
39
+    int width;
40
+    int height;
41
+    int margin;
42
+    int fg;
43
+    int bg;
44
+
45
+    struct text_font *font;
46
+};
47
+
48
+void text_prepare_font(struct text_font *tf);
49
+void text_draw(struct text_conf *tc);
50
+
51
+void text_box(const char *s);
52
+
53
+#endif // __TEXT_H__

+ 125
- 0
include/tusb_config.h Bestand weergeven

@@ -0,0 +1,125 @@
1
+/*
2
+ * Extended from TinyUSB example code.
3
+ *
4
+ * Copyright (c) 2022 - 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * The MIT License (MIT)
7
+ *
8
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
9
+ *
10
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ * of this software and associated documentation files (the "Software"), to deal
12
+ * in the Software without restriction, including without limitation the rights
13
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ * copies of the Software, and to permit persons to whom the Software is
15
+ * furnished to do so, subject to the following conditions:
16
+ *
17
+ * The above copyright notice and this permission notice shall be included in
18
+ * all copies or substantial portions of the Software.
19
+ *
20
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26
+ * THE SOFTWARE.
27
+ *
28
+ */
29
+
30
+#ifndef _TUSB_CONFIG_H_
31
+#define _TUSB_CONFIG_H_
32
+
33
+#ifdef __cplusplus
34
+ extern "C" {
35
+#endif
36
+
37
+//--------------------------------------------------------------------
38
+// COMMON CONFIGURATION
39
+//--------------------------------------------------------------------
40
+
41
+// defined by board.mk
42
+#ifndef CFG_TUSB_MCU
43
+  #error CFG_TUSB_MCU must be defined
44
+#endif
45
+
46
+// RHPort number used for device can be defined by board.mk, default to port 0
47
+#ifndef BOARD_DEVICE_RHPORT_NUM
48
+  #define BOARD_DEVICE_RHPORT_NUM     0
49
+#endif
50
+
51
+// RHPort max operational speed can defined by board.mk
52
+// Default to Highspeed for MCU with internal HighSpeed PHY (can be port specific), otherwise FullSpeed
53
+#ifndef BOARD_DEVICE_RHPORT_SPEED
54
+  #if (CFG_TUSB_MCU == OPT_MCU_LPC18XX || CFG_TUSB_MCU == OPT_MCU_LPC43XX || CFG_TUSB_MCU == OPT_MCU_MIMXRT10XX || \
55
+       CFG_TUSB_MCU == OPT_MCU_NUC505  || CFG_TUSB_MCU == OPT_MCU_CXD56 || CFG_TUSB_MCU == OPT_MCU_SAMX7X)
56
+    #define BOARD_DEVICE_RHPORT_SPEED   OPT_MODE_HIGH_SPEED
57
+  #else
58
+    #define BOARD_DEVICE_RHPORT_SPEED   OPT_MODE_FULL_SPEED
59
+  #endif
60
+#endif
61
+
62
+// Device mode with rhport and speed defined by board.mk
63
+#if   BOARD_DEVICE_RHPORT_NUM == 0
64
+  #define CFG_TUSB_RHPORT0_MODE     (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED)
65
+#elif BOARD_DEVICE_RHPORT_NUM == 1
66
+  #define CFG_TUSB_RHPORT1_MODE     (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED)
67
+#else
68
+  #error "Incorrect RHPort configuration"
69
+#endif
70
+
71
+#ifndef CFG_TUSB_OS
72
+#define CFG_TUSB_OS               OPT_OS_NONE
73
+#endif
74
+
75
+// CFG_TUSB_DEBUG is defined by compiler in DEBUG build
76
+// #define CFG_TUSB_DEBUG           0
77
+
78
+/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment.
79
+ * Tinyusb use follows macros to declare transferring memory so that they can be put
80
+ * into those specific section.
81
+ * e.g
82
+ * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") ))
83
+ * - CFG_TUSB_MEM_ALIGN   : __attribute__ ((aligned(4)))
84
+ */
85
+#ifndef CFG_TUSB_MEM_SECTION
86
+#define CFG_TUSB_MEM_SECTION
87
+#endif
88
+
89
+#ifndef CFG_TUSB_MEM_ALIGN
90
+#define CFG_TUSB_MEM_ALIGN          __attribute__ ((aligned(4)))
91
+#endif
92
+
93
+//--------------------------------------------------------------------
94
+// DEVICE CONFIGURATION
95
+//--------------------------------------------------------------------
96
+
97
+#ifndef CFG_TUD_ENDPOINT0_SIZE
98
+#define CFG_TUD_ENDPOINT0_SIZE    64
99
+#endif
100
+
101
+//------------- CLASS -------------//
102
+#define CFG_TUD_HID               0
103
+#define CFG_TUD_CDC               1
104
+#define CFG_TUD_MSC               1
105
+#define CFG_TUD_MIDI              0
106
+#define CFG_TUD_VENDOR            0
107
+
108
+// HID buffer size Should be sufficient to hold ID (if any) + Data
109
+#define CFG_TUD_HID_EP_BUFSIZE    16
110
+
111
+// CDC FIFO size of TX and RX
112
+#define CFG_TUD_CDC_RX_BUFSIZE   (TUD_OPT_HIGH_SPEED ? 512 : 64)
113
+#define CFG_TUD_CDC_TX_BUFSIZE   (TUD_OPT_HIGH_SPEED ? 512 : 64)
114
+
115
+// CDC Endpoint transfer buffer size, more is faster
116
+#define CFG_TUD_CDC_EP_BUFSIZE   (TUD_OPT_HIGH_SPEED ? 512 : 64)
117
+
118
+// MSC Buffer size of Device Mass storage
119
+#define CFG_TUD_MSC_EP_BUFSIZE   512
120
+
121
+#ifdef __cplusplus
122
+ }
123
+#endif
124
+
125
+#endif /* _TUSB_CONFIG_H_ */

+ 25
- 0
include/usb.h Bestand weergeven

@@ -0,0 +1,25 @@
1
+/*
2
+ * usb.h
3
+ *
4
+ * Copyright (c) 2022 - 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#ifndef __USB_H__
20
+#define __USB_H__
21
+
22
+void usb_init(void);
23
+void usb_run(void);
24
+
25
+#endif // __USB_H__

+ 29
- 0
include/usb_cdc.h Bestand weergeven

@@ -0,0 +1,29 @@
1
+/*
2
+ * usb_cdc.h
3
+ *
4
+ * Copyright (c) 2022 - 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#ifndef __USB_CDC_H__
20
+#define __USB_CDC_H__
21
+
22
+#include <stddef.h>
23
+#include <stdint.h>
24
+#include <stdbool.h>
25
+
26
+void usb_cdc_write(const uint8_t *buf, size_t count);
27
+void usb_cdc_set_reroute(bool reroute);
28
+
29
+#endif // __USB_CDC_H__

+ 24
- 0
include/usb_descriptors.h Bestand weergeven

@@ -0,0 +1,24 @@
1
+/*
2
+ * usb_descriptors.h
3
+ *
4
+ * Copyright (c) 2022 - 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#ifndef USB_DESCRIPTORS_H_
20
+#define USB_DESCRIPTORS_H_
21
+
22
+void usb_descriptor_init_id(void);
23
+
24
+#endif /* USB_DESCRIPTORS_H_ */

+ 25
- 0
include/usb_msc.h Bestand weergeven

@@ -0,0 +1,25 @@
1
+/*
2
+ * usb_msc.h
3
+ *
4
+ * Copyright (c) 2022 - 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#ifndef __USB_MSC_H__
20
+#define __USB_MSC_H__
21
+
22
+bool msc_is_medium_available(void);
23
+void msc_set_medium_available(bool state);
24
+
25
+#endif // __USB_MSC_H__

+ 36
- 0
include/util.h Bestand weergeven

@@ -0,0 +1,36 @@
1
+/*
2
+ * util.h
3
+ *
4
+ * Copyright (c) 2022 - 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#ifndef __UTIL_H__
20
+#define __UTIL_H__
21
+
22
+void heartbeat_init(void);
23
+void heartbeat_run(void);
24
+
25
+int32_t convert_two_complement(int32_t b);
26
+
27
+bool str_startswith(const char *str, const char *start);
28
+
29
+void reset_to_bootloader(void);
30
+void reset_to_main(void);
31
+
32
+void hexdump(const uint8_t *buff, size_t len);
33
+
34
+float map(float value, float leftMin, float leftMax, float rightMin, float rightMax);
35
+
36
+#endif // __UTIL_H__

+ 36
- 0
include/volcano.h Bestand weergeven

@@ -0,0 +1,36 @@
1
+/*
2
+ * volcano.h
3
+ *
4
+ * Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#ifndef __VOLCANO_H__
20
+#define __VOLCANO_H__
21
+
22
+#include <stdint.h>
23
+#include <stdbool.h>
24
+
25
+// in 1/10th degrees C, or < 0 on error
26
+int16_t volcano_get_current_temp(void);
27
+int16_t volcano_get_target_temp(void);
28
+
29
+// v in 1/10th degrees C, returns < 0 on error
30
+int8_t volcano_set_target_temp(uint16_t v);
31
+
32
+// returns < 0 on error
33
+int8_t volcano_set_heater_state(bool value);
34
+int8_t volcano_set_pump_state(bool value);
35
+
36
+#endif // __VOLCANO_H__

+ 60
- 0
include/workflow.h Bestand weergeven

@@ -0,0 +1,60 @@
1
+/*
2
+ * workflow.h
3
+ *
4
+ * Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#ifndef __WORKFLOW_H__
20
+#define __WORKFLOW_H__
21
+
22
+#include <stdint.h>
23
+
24
+enum wf_op {
25
+    OP_SET_TEMPERATURE = 0,
26
+    OP_WAIT_TEMPERATURE,
27
+    OP_WAIT_TIME,
28
+    OP_PUMP_TIME,
29
+};
30
+
31
+struct wf_step {
32
+    enum wf_op op;
33
+    uint16_t val;
34
+};
35
+
36
+enum wf_status {
37
+    WF_IDLE = 0,
38
+    WF_RUNNING,
39
+};
40
+
41
+struct wf_state {
42
+    enum wf_status status;
43
+
44
+    uint16_t index;
45
+    uint16_t count;
46
+    struct wf_step step;
47
+    uint16_t start_val, curr_val;
48
+};
49
+
50
+uint16_t wf_count(void);
51
+const char *wf_name(uint16_t index);
52
+const char *wf_author(uint16_t index);
53
+
54
+struct wf_state wf_status(void);
55
+void wf_start(uint16_t index);
56
+
57
+void wf_reset(void);
58
+void wf_run(void);
59
+
60
+#endif // __WORKFLOW_H__

+ 1
- 0
mcufont

@@ -0,0 +1 @@
1
+Subproject commit 9f3aa41b231195e7b2b59f78d8f01d06460b6d35

+ 22
- 0
pack_data.sh Bestand weergeven

@@ -0,0 +1,22 @@
1
+#!/bin/bash
2
+set -euo pipefail
3
+
4
+cd "$(dirname "$0")"
5
+echo "Packing data"
6
+
7
+rm -rf build/src
8
+mkdir -p build/src
9
+cp COPYING build/src
10
+cp README.md build/src
11
+cp CMakeLists.txt build/src
12
+cp .gitmodules build/src/gitmodules
13
+cp -r include build/src
14
+cp -r src build/src
15
+
16
+cd build
17
+rm -rf data.tar data.tar.xz
18
+tar -f data.tar -c src
19
+xz -z -9 data.tar
20
+
21
+xxd -i data.tar.xz > pack_data.h
22
+sed -i 's/unsigned/static const unsigned/g' pack_data.h

+ 1
- 0
pico-sdk

@@ -0,0 +1 @@
1
+Subproject commit 6a7db34ff63345a7badec79ebea3aaef1712f374

+ 16
- 0
python-test/copy.sh Bestand weergeven

@@ -1,5 +1,21 @@
1 1
 #!/bin/bash
2 2
 
3
+# ----------------------------------------------------------------------------
4
+# Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+#
6
+# This program is free software: you can redistribute it and/or modify
7
+# it under the terms of the GNU General Public License as published by
8
+# the Free Software Foundation, either version 3 of the License, or
9
+# (at your option) any later version.
10
+#
11
+# This program is distributed in the hope that it will be useful,
12
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+# GNU General Public License for more details.
15
+#
16
+# See <http://www.gnu.org/licenses/>.
17
+# ----------------------------------------------------------------------------
18
+
3 19
 BRANCH=`git symbolic-ref --short HEAD`
4 20
 HASH=`git rev-parse --short HEAD`
5 21
 DATE=`date "+%Y-%m-%d %H:%M:%S"`

+ 48
- 2
python-test/lcd.py Bestand weergeven

@@ -1,3 +1,21 @@
1
+#!/usr/bin/env python3
2
+
3
+# ----------------------------------------------------------------------------
4
+# Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+#
6
+# This program is free software: you can redistribute it and/or modify
7
+# it under the terms of the GNU General Public License as published by
8
+# the Free Software Foundation, either version 3 of the License, or
9
+# (at your option) any later version.
10
+#
11
+# This program is distributed in the hope that it will be useful,
12
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+# GNU General Public License for more details.
15
+#
16
+# See <http://www.gnu.org/licenses/>.
17
+# ----------------------------------------------------------------------------
18
+
1 19
 # https://www.waveshare.com/wiki/Pico-LCD-1.3
2 20
 # https://thepihut.com/blogs/raspberry-pi-tutorials/coding-graphics-with-micropython-on-raspberry-pi-pico-displays
3 21
 # https://api.arcade.academy/en/latest/_modules/arcade/draw_commands.html#draw_arc_filled
@@ -18,6 +36,9 @@ class KeyCheck:
18 36
     def once(self, k):
19 37
         return self.new[k] and not self.old[k]
20 38
 
39
+    def held(self, k):
40
+        return self.new[k]
41
+
21 42
 class LCD(framebuf.FrameBuffer):
22 43
     def __init__(self):
23 44
         self.pwm = PWM(Pin(13))
@@ -32,8 +53,6 @@ class LCD(framebuf.FrameBuffer):
32 53
 
33 54
         self.cs(1)
34 55
 
35
-        #self.spi = SPI(1)
36
-        #self.spi = SPI(1,   1_000_000)
37 56
         self.spi = SPI(1, 100_000_000, polarity=0, phase=0, sck=Pin(10), mosi=Pin(11), miso=None)
38 57
 
39 58
         self.dc = Pin(8, Pin.OUT)
@@ -77,6 +96,15 @@ class LCD(framebuf.FrameBuffer):
77 96
             "enter": False,
78 97
         }
79 98
 
99
+        self.curr_brightness = 0.0
100
+        try:
101
+            with open("_cur_bright.txt", "r") as f:
102
+                s = next(f).split()[0]
103
+                self.curr_brightness = float(s)
104
+        except:
105
+            pass
106
+        self.brightness(self.curr_brightness)
107
+
80 108
     def buttons(self):
81 109
         keys = {
82 110
             "a": self.keyA.value() == 0,
@@ -97,7 +125,25 @@ class LCD(framebuf.FrameBuffer):
97 125
     def color(self, R, G, B):
98 126
         return (((G & 0b00011100) << 3) + ((B & 0b11111000) >> 3) << 8) + (R & 0b11111000) + ((G & 0b11100000) >> 5)
99 127
 
128
+    def store_brightness(self):
129
+        old = -1.0
130
+        try:
131
+            with open("_cur_bright.txt", "r") as f:
132
+                s = next(f).split()[0]
133
+                old = float(s)
134
+        except:
135
+            pass
136
+        if self.curr_brightness != old:
137
+            with open("_cur_bright.txt", "w") as f:
138
+                s = "{}\n".format(self.curr_brightness)
139
+                f.write(s)
140
+
100 141
     def brightness(self, v):
142
+        if v < 0.0:
143
+            v = 0.0
144
+        if v > 1.0:
145
+            v = 1.0
146
+        self.curr_brightness = v
101 147
         self.pwm.duty_u16(int(v * 65535))
102 148
 
103 149
     def write_cmd(self, cmd):

+ 187
- 0
python-test/ota.py Bestand weergeven

@@ -0,0 +1,187 @@
1
+#!/usr/bin/env python3
2
+
3
+# Uses the Gitea API to fetch the latest revision of the project from a repo.
4
+#
5
+# Inspired by:
6
+# https://github.com/olivergregorius/micropython_ota
7
+#
8
+# ----------------------------------------------------------------------------
9
+# Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
10
+#
11
+# This program is free software: you can redistribute it and/or modify
12
+# it under the terms of the GNU General Public License as published by
13
+# the Free Software Foundation, either version 3 of the License, or
14
+# (at your option) any later version.
15
+#
16
+# This program is distributed in the hope that it will be useful,
17
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
18
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19
+# GNU General Public License for more details.
20
+#
21
+# See <http://www.gnu.org/licenses/>.
22
+# ----------------------------------------------------------------------------
23
+
24
+import util
25
+import sys
26
+import os
27
+
28
+class StateUpdate:
29
+    def __init__(self, lcd):
30
+        self.host = "https://git.xythobuz.de"
31
+        self.repo = "thomas/sb-py"
32
+        self.branch = None
33
+        self.exe_path = "states.py"
34
+        self.blacklist = [
35
+            "README.md",
36
+            "COPYING",
37
+            ".gitignore",
38
+            "python-test/.gitignore",
39
+            "python-test/copy.sh",
40
+            "web-app/fetch.sh",
41
+        ]
42
+
43
+        self.lcd = lcd
44
+
45
+        self.get = None
46
+        self.version_file = "_ota_version"
47
+
48
+    def fetch(self, url):
49
+        # lazily initialize WiFi
50
+        if self.get == None:
51
+            self.get, post = util.getRequests()
52
+            if self.get == None:
53
+                return None
54
+
55
+        try:
56
+            #print("GET " + url)
57
+            r = self.get(url)
58
+
59
+            # explitic close on Response object not needed,
60
+            # handled internally by r.content / r.text / r.json()
61
+            # to avoid this automatic behaviour, first access r.content
62
+            # to trigger caching it in response object, then close
63
+            # socket.
64
+            tmp = r.content
65
+            if hasattr(r, "raw"):
66
+                if r.raw != None:
67
+                    r.raw.close()
68
+                    r.raw = None
69
+
70
+            return r
71
+        except Exception as e:
72
+            print()
73
+            print(url)
74
+            if hasattr(sys, "print_exception"):
75
+                sys.print_exception(e)
76
+            else:
77
+                print(e)
78
+            print()
79
+            return None
80
+
81
+    def get_stored_commit(self):
82
+        current = "unknown"
83
+        try:
84
+            f = open(self.version_file, "r")
85
+            current = f.readline().strip()
86
+            f.close()
87
+        except Exception as e:
88
+            print()
89
+            if hasattr(sys, "print_exception"):
90
+                sys.print_exception(e)
91
+            else:
92
+                print(e)
93
+            print()
94
+        return current
95
+
96
+    def get_previous_commit(self, commit):
97
+        r = self.fetch(self.host + "/" + self.repo + "/commit/" + commit).text
98
+        for line in r.splitlines():
99
+            if not (self.repo + "/commit/") in line:
100
+                continue
101
+
102
+            line = line[line.find("/commit/") : ][8 : ][ : 40]
103
+            if line != commit:
104
+                return line
105
+        return "unknown"
106
+
107
+    def check(self, verbose = False):
108
+        if self.branch == None:
109
+            # get default branch
110
+            r = self.fetch(self.host + "/api/v1/repos/" + self.repo).json()
111
+            self.branch = r["default_branch"]
112
+
113
+            if verbose:
114
+                print("Selected default branch " + self.branch)
115
+
116
+        # check for latest commit in branch
117
+        r = self.fetch(self.host + "/api/v1/repos/" + self.repo + "/branches/" + self.branch).json()
118
+        commit = r["commit"]["id"]
119
+
120
+        if verbose:
121
+            print("Latest commit is " + commit)
122
+
123
+        current = self.get_stored_commit()
124
+
125
+        if verbose:
126
+            if current != commit:
127
+                print("Current commit " + current + " is different!")
128
+            else:
129
+                print("No update required")
130
+
131
+        return (current != commit, commit)
132
+
133
+    def update_to_commit(self, commit, verbose = False):
134
+        # list all files for a commit
135
+        r = self.fetch(self.host + "/api/v1/repos/" + self.repo + "/git/trees/" + commit).json()
136
+
137
+        # TODO does not support sub-folders
138
+
139
+        if verbose:
140
+            if len(r["tree"]) > 0:
141
+                print(str(len(r["tree"])) + " files in repo:")
142
+                for f in r["tree"]:
143
+                    if f["path"] in self.blacklist:
144
+                        print("  - (IGNORED) " + f["path"])
145
+                    else:
146
+                        print("  - " + f["path"])
147
+            else:
148
+                print("No files in repo?!")
149
+
150
+        for f in r["tree"]:
151
+            if f["path"] in self.blacklist:
152
+                continue
153
+
154
+            # get a file from a commit
155
+            r = self.fetch(self.host + "/" + self.repo + "/raw/commit/" + commit + "/" + f["path"]).text
156
+
157
+            if verbose:
158
+                print("Writing " + f["path"])
159
+
160
+            # overwrite existing file
161
+            fo = open(f["path"], "w")
162
+            fo.write(r)
163
+            fo.close()
164
+
165
+            if f["path"] == self.exe_path:
166
+                if verbose:
167
+                    print("Writing " + f["path"] + " to main.py")
168
+
169
+                fo = open("./main.py", "w")
170
+                fo.write(r)
171
+                fo.close()
172
+
173
+        # Write new commit id to local file
174
+        f = open(self.version_file, "w")
175
+        f.write(commit + "\n")
176
+        f.close()
177
+
178
+def pico_ota_run():
179
+    print("Checking for updates")
180
+    newer, commit = ota.check(True)
181
+
182
+    if newer:
183
+        print("Updating to:", commit)
184
+        ota.update_to_commit(commit, True)
185
+
186
+        print("Resetting")
187
+        machine.soft_reset()

+ 17
- 1
python-test/poll.py Bestand weergeven

@@ -1,4 +1,20 @@
1
-#!/usr/bin/env python
1
+#!/usr/bin/env python3
2
+
3
+# ----------------------------------------------------------------------------
4
+# Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+#
6
+# This program is free software: you can redistribute it and/or modify
7
+# it under the terms of the GNU General Public License as published by
8
+# the Free Software Foundation, either version 3 of the License, or
9
+# (at your option) any later version.
10
+#
11
+# This program is distributed in the hope that it will be useful,
12
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+# GNU General Public License for more details.
15
+#
16
+# See <http://www.gnu.org/licenses/>.
17
+# ----------------------------------------------------------------------------
2 18
 
3 19
 import uasyncio as asyncio
4 20
 import bluetooth

+ 18
- 0
python-test/scan.py Bestand weergeven

@@ -1,3 +1,21 @@
1
+#!/usr/bin/env python3
2
+
3
+# ----------------------------------------------------------------------------
4
+# Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+#
6
+# This program is free software: you can redistribute it and/or modify
7
+# it under the terms of the GNU General Public License as published by
8
+# the Free Software Foundation, either version 3 of the License, or
9
+# (at your option) any later version.
10
+#
11
+# This program is distributed in the hope that it will be useful,
12
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+# GNU General Public License for more details.
15
+#
16
+# See <http://www.gnu.org/licenses/>.
17
+# ----------------------------------------------------------------------------
18
+
1 19
 # https://github.com/micropython/micropython-lib/blob/master/micropython/bluetooth/aioble/examples/temp_client.py
2 20
 
3 21
 import uasyncio as asyncio

+ 17
- 1
python-test/state_connect.py Bestand weergeven

@@ -1,4 +1,20 @@
1
-#!/usr/bin/env python
1
+#!/usr/bin/env python3
2
+
3
+# ----------------------------------------------------------------------------
4
+# Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+#
6
+# This program is free software: you can redistribute it and/or modify
7
+# it under the terms of the GNU General Public License as published by
8
+# the Free Software Foundation, either version 3 of the License, or
9
+# (at your option) any later version.
10
+#
11
+# This program is distributed in the hope that it will be useful,
12
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+# GNU General Public License for more details.
15
+#
16
+# See <http://www.gnu.org/licenses/>.
17
+# ----------------------------------------------------------------------------
2 18
 
3 19
 import uasyncio as asyncio
4 20
 from poll import cache_services_characteristics

+ 17
- 1
python-test/state_heat.py Bestand weergeven

@@ -1,4 +1,20 @@
1
-#!/usr/bin/env python
1
+#!/usr/bin/env python3
2
+
3
+# ----------------------------------------------------------------------------
4
+# Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+#
6
+# This program is free software: you can redistribute it and/or modify
7
+# it under the terms of the GNU General Public License as published by
8
+# the Free Software Foundation, either version 3 of the License, or
9
+# (at your option) any later version.
10
+#
11
+# This program is distributed in the hope that it will be useful,
12
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+# GNU General Public License for more details.
15
+#
16
+# See <http://www.gnu.org/licenses/>.
17
+# ----------------------------------------------------------------------------
2 18
 
3 19
 import uasyncio as asyncio
4 20
 from poll import set_state, set_target_temp

+ 17
- 1
python-test/state_notify.py Bestand weergeven

@@ -1,4 +1,20 @@
1
-#!/usr/bin/env python
1
+#!/usr/bin/env python3
2
+
3
+# ----------------------------------------------------------------------------
4
+# Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+#
6
+# This program is free software: you can redistribute it and/or modify
7
+# it under the terms of the GNU General Public License as published by
8
+# the Free Software Foundation, either version 3 of the License, or
9
+# (at your option) any later version.
10
+#
11
+# This program is distributed in the hope that it will be useful,
12
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+# GNU General Public License for more details.
15
+#
16
+# See <http://www.gnu.org/licenses/>.
17
+# ----------------------------------------------------------------------------
2 18
 
3 19
 import time
4 20
 import uasyncio as asyncio

+ 17
- 1
python-test/state_pump.py Bestand weergeven

@@ -1,4 +1,20 @@
1
-#!/usr/bin/env python
1
+#!/usr/bin/env python3
2
+
3
+# ----------------------------------------------------------------------------
4
+# Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+#
6
+# This program is free software: you can redistribute it and/or modify
7
+# it under the terms of the GNU General Public License as published by
8
+# the Free Software Foundation, either version 3 of the License, or
9
+# (at your option) any later version.
10
+#
11
+# This program is distributed in the hope that it will be useful,
12
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+# GNU General Public License for more details.
15
+#
16
+# See <http://www.gnu.org/licenses/>.
17
+# ----------------------------------------------------------------------------
2 18
 
3 19
 import time
4 20
 import uasyncio as asyncio

+ 34
- 2
python-test/state_scan.py Bestand weergeven

@@ -1,4 +1,20 @@
1
-#!/usr/bin/env python
1
+#!/usr/bin/env python3
2
+
3
+# ----------------------------------------------------------------------------
4
+# Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+#
6
+# This program is free software: you can redistribute it and/or modify
7
+# it under the terms of the GNU General Public License as published by
8
+# the Free Software Foundation, either version 3 of the License, or
9
+# (at your option) any later version.
10
+#
11
+# This program is distributed in the hope that it will be useful,
12
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+# GNU General Public License for more details.
15
+#
16
+# See <http://www.gnu.org/licenses/>.
17
+# ----------------------------------------------------------------------------
2 18
 
3 19
 import uasyncio as asyncio
4 20
 from scan import ble_scan
@@ -23,6 +39,8 @@ class StateScan:
23 39
         if self.lock.locked():
24 40
             self.lock.release()
25 41
 
42
+        self.lcd.store_brightness()
43
+
26 44
         return self.results[self.current][4]
27 45
 
28 46
     async def scan(self):
@@ -83,7 +101,7 @@ class StateScan:
83 101
             if self.current == i:
84 102
                 c2 = self.lcd.red
85 103
 
86
-            self.lcd.hline(0, off, self.lcd.width, self.lcd.blue)
104
+            self.lcd.hline(0, off - 3, self.lcd.width, self.lcd.blue)
87 105
             self.lcd.text(s1, 0, off + 2, c1)
88 106
             self.lcd.text(s2, 0, off + 12, c2)
89 107
 
@@ -106,6 +124,17 @@ class StateScan:
106 124
                     self.current = 0
107 125
                 else:
108 126
                     self.current += 1
127
+            elif keys.once("x"):
128
+                return 10
129
+            elif keys.held("left"):
130
+                v = self.lcd.curr_brightness - 0.05
131
+                if v < 0.05:
132
+                    v = 0.05
133
+                self.lcd.brightness(v)
134
+            elif keys.held("right"):
135
+                self.lcd.brightness(self.lcd.curr_brightness + 0.05)
136
+            elif keys.once("y"):
137
+                return 0
109 138
 
110 139
             if self.current != None:
111 140
                 while self.current < 0:
@@ -129,4 +158,7 @@ class StateScan:
129 158
 
130 159
             self.draw_list()
131 160
 
161
+        w = int(self.lcd.width * self.lcd.curr_brightness)
162
+        self.lcd.hline(0, 24, w, self.lcd.white)
163
+
132 164
         return -1

+ 29
- 2
python-test/state_select.py Bestand weergeven

@@ -1,4 +1,20 @@
1
-#!/usr/bin/env python
1
+#!/usr/bin/env python3
2
+
3
+# ----------------------------------------------------------------------------
4
+# Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+#
6
+# This program is free software: you can redistribute it and/or modify
7
+# it under the terms of the GNU General Public License as published by
8
+# the Free Software Foundation, either version 3 of the License, or
9
+# (at your option) any later version.
10
+#
11
+# This program is distributed in the hope that it will be useful,
12
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+# GNU General Public License for more details.
15
+#
16
+# See <http://www.gnu.org/licenses/>.
17
+# ----------------------------------------------------------------------------
2 18
 
3 19
 import uasyncio as asyncio
4 20
 from workflows import workflows
@@ -13,6 +29,7 @@ class StateSelect:
13 29
         self.menuOff = 0
14 30
 
15 31
     def exit(self):
32
+        self.lcd.store_brightness()
16 33
         return self.client, workflows[self.current]
17 34
 
18 35
     def draw_list(self):
@@ -31,7 +48,7 @@ class StateSelect:
31 48
             if self.current == i:
32 49
                 c = self.lcd.red
33 50
 
34
-            self.lcd.hline(0, off, self.lcd.width, self.lcd.blue)
51
+            self.lcd.hline(0, off - 3, self.lcd.width, self.lcd.blue)
35 52
             self.lcd.text(s1, 0, off + 2, c)
36 53
             self.lcd.text(s2, 0, off + 12, c)
37 54
 
@@ -48,6 +65,13 @@ class StateSelect:
48 65
             self.current += 1
49 66
         elif keys.once("enter") or keys.once("a"):
50 67
             return 1
68
+        elif keys.held("left"):
69
+            v = self.lcd.curr_brightness - 0.05
70
+            if v < 0.05:
71
+                v = 0.05
72
+            self.lcd.brightness(v)
73
+        elif keys.held("right"):
74
+            self.lcd.brightness(self.lcd.curr_brightness + 0.05)
51 75
 
52 76
         while self.current < 0:
53 77
             self.current += len(workflows)
@@ -60,4 +84,7 @@ class StateSelect:
60 84
 
61 85
         self.draw_list()
62 86
 
87
+        w = int(self.lcd.width * self.lcd.curr_brightness)
88
+        self.lcd.hline(0, 24, w, self.lcd.white)
89
+
63 90
         return -1

+ 17
- 1
python-test/state_wait_temp.py Bestand weergeven

@@ -1,4 +1,20 @@
1
-#!/usr/bin/env python
1
+#!/usr/bin/env python3
2
+
3
+# ----------------------------------------------------------------------------
4
+# Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+#
6
+# This program is free software: you can redistribute it and/or modify
7
+# it under the terms of the GNU General Public License as published by
8
+# the Free Software Foundation, either version 3 of the License, or
9
+# (at your option) any later version.
10
+#
11
+# This program is distributed in the hope that it will be useful,
12
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+# GNU General Public License for more details.
15
+#
16
+# See <http://www.gnu.org/licenses/>.
17
+# ----------------------------------------------------------------------------
2 18
 
3 19
 import uasyncio as asyncio
4 20
 from poll import set_target_temp, get_current_temp

+ 17
- 1
python-test/state_wait_time.py Bestand weergeven

@@ -1,4 +1,20 @@
1
-#!/usr/bin/env python
1
+#!/usr/bin/env python3
2
+
3
+# ----------------------------------------------------------------------------
4
+# Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+#
6
+# This program is free software: you can redistribute it and/or modify
7
+# it under the terms of the GNU General Public License as published by
8
+# the Free Software Foundation, either version 3 of the License, or
9
+# (at your option) any later version.
10
+#
11
+# This program is distributed in the hope that it will be useful,
12
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+# GNU General Public License for more details.
15
+#
16
+# See <http://www.gnu.org/licenses/>.
17
+# ----------------------------------------------------------------------------
2 18
 
3 19
 import time
4 20
 from state_wait_temp import draw_graph

+ 59
- 17
python-test/states.py Bestand weergeven

@@ -1,4 +1,23 @@
1
-#!/usr/bin/env python
1
+#!/usr/bin/env python3
2
+
3
+# ----------------------------------------------------------------------------
4
+# Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+#
6
+# This program is free software: you can redistribute it and/or modify
7
+# it under the terms of the GNU General Public License as published by
8
+# the Free Software Foundation, either version 3 of the License, or
9
+# (at your option) any later version.
10
+#
11
+# This program is distributed in the hope that it will be useful,
12
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+# GNU General Public License for more details.
15
+#
16
+# See <http://www.gnu.org/licenses/>.
17
+# ----------------------------------------------------------------------------
18
+
19
+from lcd import LCD
20
+lcd = LCD()
2 21
 
3 22
 import uasyncio as asyncio
4 23
 import io
@@ -7,13 +26,16 @@ import machine
7 26
 import os
8 27
 import gc
9 28
 import time
29
+from state_wait_temp import from_hsv, translate
10 30
 
11 31
 # https://github.com/pimoroni/pimoroni-pico/blob/main/micropython/examples/pico_lipo_shim/battery_pico.py
12 32
 # https://github.com/pimoroni/enviro/pull/146
13 33
 # TODO https://github.com/micropython/micropython/issues/11185
14 34
 
15
-full_battery = 4.2
35
+full_battery = 4.1
16 36
 empty_battery = 3.2
37
+batt_warn_limit = 15
38
+batt_reread_limit = 2.7
17 39
 
18 40
 charging = machine.Pin("WL_GPIO2", machine.Pin.IN)
19 41
 conversion_factor = 3 * 3.3 / 65535
@@ -35,7 +57,7 @@ def batteryVoltageAverage():
35 57
     old_pad = get_pad(29)
36 58
     set_pad(29, 128)  # no pulls, no output, no input
37 59
 
38
-    sample_count = 10
60
+    sample_count = 3
39 61
     voltage = 0
40 62
     for i in range(0, sample_count):
41 63
         voltage += batteryVoltageRead()
@@ -47,13 +69,17 @@ def batteryVoltageAverage():
47 69
 def batteryVoltage():
48 70
     global cachedVoltage, lastCaching
49 71
 
50
-    if ((time.time() - lastCaching) >= 2) or (cachedVoltage == None):
72
+    if ((time.time() - lastCaching) > 0) or (cachedVoltage == None):
51 73
         lastCaching = time.time()
52 74
         cachedVoltage = batteryVoltageAverage()
75
+        if cachedVoltage <= batt_reread_limit:
76
+            cachedVoltage = batteryVoltageAverage()
53 77
 
54 78
     percentage = 100.0 * ((cachedVoltage - empty_battery) / (full_battery - empty_battery))
55
-    if percentage > 100.0:
56
-        percentage = 100.0
79
+    if percentage >= 100.0:
80
+        percentage = 99.0
81
+    elif percentage < 0.0:
82
+        percentage = 0.0
57 83
 
58 84
     return cachedVoltage, percentage
59 85
 
@@ -70,20 +96,35 @@ class States:
70 96
         self.lcd.fill(self.lcd.black)
71 97
         self.lcd.text("Volcano Remote Control App", 0, 0, self.lcd.green)
72 98
 
73
-        r = await self.states[self.current].draw()
99
+        ret = await self.states[self.current].draw()
74 100
 
75 101
         voltage, percentage = batteryVoltage()
76
-        s = "Charging ({:.2f}V)".format(voltage)
77
-        c = self.lcd.green
102
+        s = "Charging"
103
+        c = self.lcd.white
78 104
         if charging.value() != 1:
79 105
             s = "{:.0f}% ({:.2f}V)".format(percentage, voltage)
80
-            c = self.lcd.white
81
-            if percentage <= 20:
106
+            if percentage <= batt_warn_limit:
82 107
                 c = self.lcd.red
83
-        self.lcd.text("Battery: {}".format(s), 0, self.lcd.height - 10, c)
108
+            else:
109
+                hue = translate(percentage, batt_warn_limit, 100, 0.0, 0.333)
110
+                r, g, b = from_hsv(hue, 1.0, 1.0)
111
+                c = self.lcd.color(r, g, b)
112
+        whole = "Batt: {}".format(s)
113
+        self.lcd.text(whole, 0, self.lcd.height - 10, c)
114
+
115
+        off = (len(whole) + 1) * 8
116
+        if percentage <= batt_warn_limit:
117
+            self.lcd.text("CHARGE NOW!", off, self.lcd.height - 10, self.lcd.red)
118
+        elif charging.value() != 1:
119
+            self.lcd.rect(off, self.lcd.height - 10, self.lcd.width - off, 8, c, False)
120
+            max_w = self.lcd.width - off - 2
121
+            w = int(percentage / 100.0 * max_w)
122
+            self.lcd.rect(off + 1, self.lcd.height - 9, w, 6, c, True)
123
+        else:
124
+            pass # TODO charge indicator (lightning bolt?)
84 125
 
85 126
         self.lcd.show()
86
-        return r
127
+        return ret
87 128
 
88 129
     def run(self):
89 130
         if self.current == None:
@@ -147,12 +188,14 @@ def state_machine(lcd):
147 188
     notify = StateNotify(lcd)
148 189
     states.add(notify)
149 190
 
191
+    # 10 - OTA Update
192
+    #from ota import StateUpdate
193
+    #update = StateUpdate(lcd)
194
+    #states.add(update)
195
+
150 196
     while True:
151 197
         states.run()
152 198
 
153
-from lcd import LCD
154
-lcd = LCD()
155
-
156 199
 def main():
157 200
     # splash screen
158 201
     from state_wait_temp import from_hsv
@@ -178,7 +221,6 @@ def main():
178 221
     lcd.textC(os.uname()[4][30 : 60], int(lcd.width / 2), lcd.height - 10, lcd.white, lcd.black)
179 222
 
180 223
     lcd.show()
181
-    lcd.brightness(1.0)
182 224
 
183 225
     # bootloader access with face buttons
184 226
     keys = lcd.buttons()

+ 17
- 1
python-test/workflows.py Bestand weergeven

@@ -1,4 +1,20 @@
1
-#!/usr/bin/env python
1
+#!/usr/bin/env python3
2
+
3
+# ----------------------------------------------------------------------------
4
+# Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+#
6
+# This program is free software: you can redistribute it and/or modify
7
+# it under the terms of the GNU General Public License as published by
8
+# the Free Software Foundation, either version 3 of the License, or
9
+# (at your option) any later version.
10
+#
11
+# This program is distributed in the hope that it will be useful,
12
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+# GNU General Public License for more details.
15
+#
16
+# See <http://www.gnu.org/licenses/>.
17
+# ----------------------------------------------------------------------------
2 18
 
3 19
 workflows = [
4 20
     {

+ 770
- 0
src/ble.c Bestand weergeven

@@ -0,0 +1,770 @@
1
+/*
2
+ * ble.c
3
+ *
4
+ * https://github.com/raspberrypi/pico-examples/blob/master/pico_w/bt/standalone/client.c
5
+ * https://vanhunteradams.com/Pico/BLE/BTStack_HCI.html
6
+ *
7
+ * Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
8
+ *
9
+ * This program is free software: you can redistribute it and/or modify
10
+ * it under the terms of the GNU General Public License as published by
11
+ * the Free Software Foundation, either version 3 of the License, or
12
+ * (at your option) any later version.
13
+ *
14
+ * This program is distributed in the hope that it will be useful,
15
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
+ * GNU General Public License for more details.
18
+ *
19
+ * See <http://www.gnu.org/licenses/>.
20
+ */
21
+
22
+#include "pico/cyw43_arch.h"
23
+#include "hardware/watchdog.h"
24
+
25
+#include "config.h"
26
+#include "log.h"
27
+#include "main.h"
28
+#include "util.h"
29
+#include "ble.h"
30
+
31
+#define BLE_READ_TIMEOUT_MS (2 * 500)
32
+#define BLE_SRVC_TIMEOUT_MS (2 * 500)
33
+#define BLE_CHAR_TIMEOUT_MS (2 * 2000)
34
+#define BLE_WRTE_TIMEOUT_MS (2 * 500)
35
+#define BLE_MAX_SCAN_AGE_MS (10 * 1000)
36
+#define BLE_MAX_SERVICES 8
37
+#define BLE_MAX_CHARACTERISTICS 8
38
+
39
+enum ble_state {
40
+    TC_OFF = 0,
41
+    TC_IDLE,
42
+    TC_W4_SCAN,
43
+    TC_W4_CONNECT,
44
+    TC_READY,
45
+    TC_W4_READ,
46
+    TC_READ_COMPLETE,
47
+    TC_W4_SERVICE,
48
+    TC_W4_CHARACTERISTIC,
49
+    TC_W4_WRITE,
50
+    TC_WRITE_COMPLETE,
51
+};
52
+
53
+struct ble_characteristic {
54
+    bool set;
55
+    gatt_client_characteristic_t c;
56
+};
57
+
58
+struct ble_service {
59
+    bool set;
60
+    gatt_client_service_t service;
61
+    struct ble_characteristic chars[BLE_MAX_CHARACTERISTICS];
62
+};
63
+
64
+static btstack_packet_callback_registration_t hci_event_callback_registration;
65
+static hci_con_handle_t connection_handle;
66
+static enum ble_state state = TC_OFF;
67
+
68
+static struct ble_scan_result scans[BLE_MAX_SCAN_RESULTS] = {0};
69
+
70
+static uint16_t read_len = 0;
71
+static uint8_t data_buff[BLE_MAX_VALUE_LEN] = {0};
72
+
73
+static struct ble_service services[BLE_MAX_SERVICES] = {0};
74
+static uint8_t service_idx = 0;
75
+static uint8_t characteristic_idx = 0;
76
+
77
+static void hci_add_scan_result(bd_addr_t addr, bd_addr_type_t type, int8_t rssi) {
78
+    int unused = -1;
79
+
80
+    for (uint i = 0; i < BLE_MAX_SCAN_RESULTS; i++) {
81
+        if (!scans[i].set) {
82
+            if (unused < 0) {
83
+                unused = i;
84
+            }
85
+            continue;
86
+        }
87
+
88
+        if (memcmp(addr, scans[i].addr, sizeof(bd_addr_t)) == 0) {
89
+            // already in list, just update changing values
90
+            scans[i].time = to_ms_since_boot(get_absolute_time());
91
+            scans[i].rssi = rssi;
92
+            return;
93
+        }
94
+    }
95
+
96
+    if (unused < 0) {
97
+        debug("no space in scan results for %s", bd_addr_to_str(addr));
98
+        return;
99
+    }
100
+
101
+    debug("new device with addr %s", bd_addr_to_str(addr));
102
+    scans[unused].set = true;
103
+    scans[unused].time = to_ms_since_boot(get_absolute_time());
104
+    memcpy(scans[unused].addr, addr, sizeof(bd_addr_t));
105
+    scans[unused].type = type;
106
+    scans[unused].rssi = rssi;
107
+    scans[unused].name[0] = '\0';
108
+    scans[unused].data_len = 0;
109
+}
110
+
111
+static void hci_scan_result_add_name(bd_addr_t addr, const uint8_t *data, uint8_t data_size) {
112
+    for (uint i = 0; i < BLE_MAX_SCAN_RESULTS; i++) {
113
+        if (!scans[i].set) {
114
+            continue;
115
+        }
116
+        if (memcmp(addr, scans[i].addr, sizeof(bd_addr_t)) != 0) {
117
+            continue;
118
+        }
119
+
120
+        uint8_t len = data_size;
121
+        if (len > BLE_MAX_NAME_LENGTH) {
122
+            len = BLE_MAX_NAME_LENGTH;
123
+        }
124
+        memcpy(scans[i].name, data, len);
125
+        scans[i].name[len] = '\0';
126
+        scans[i].time = to_ms_since_boot(get_absolute_time());
127
+        return;
128
+    }
129
+
130
+    debug("no matching entry for %s to add name '%.*s' to", bd_addr_to_str(addr), data_size, data);
131
+}
132
+
133
+static void hci_scan_result_add_data(bd_addr_t addr, const uint8_t *data, uint8_t data_size) {
134
+    for (uint i = 0; i < BLE_MAX_SCAN_RESULTS; i++) {
135
+        if (!scans[i].set) {
136
+            continue;
137
+        }
138
+        if (memcmp(addr, scans[i].addr, sizeof(bd_addr_t)) != 0) {
139
+            continue;
140
+        }
141
+
142
+        uint8_t len = data_size;
143
+        if (len > BLE_MAX_DATA_LENGTH) {
144
+            len = BLE_MAX_DATA_LENGTH;
145
+        }
146
+        memcpy(scans[i].data, data, len);
147
+        scans[i].data_len = len;
148
+        scans[i].time = to_ms_since_boot(get_absolute_time());
149
+        return;
150
+    }
151
+
152
+    debug("no matching entry for %s to add data to", bd_addr_to_str(addr));
153
+}
154
+
155
+static void hci_event_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
156
+    UNUSED(size);
157
+    UNUSED(channel);
158
+
159
+    //debug("type=0x%02X size=%d", packet_type, size);
160
+    //hexdump(packet, size);
161
+
162
+    if (packet_type != HCI_EVENT_PACKET) {
163
+        //debug("unexpected packet 0x%02X", packet_type);
164
+        return;
165
+    }
166
+
167
+    switch (hci_event_packet_get_type(packet)) {
168
+    case BTSTACK_EVENT_STATE:
169
+            if (btstack_event_state_get_state(packet) == HCI_STATE_WORKING) {
170
+                bd_addr_t local_addr;
171
+                gap_local_bd_addr(local_addr);
172
+                debug("BTstack up with %s", bd_addr_to_str(local_addr));
173
+                state = TC_IDLE;
174
+            } else {
175
+                debug("BTstack down (%d)", btstack_event_state_get_state(packet));
176
+                state = TC_OFF;
177
+            }
178
+        break;
179
+
180
+    case GAP_EVENT_ADVERTISING_REPORT: {
181
+        if (state != TC_W4_SCAN) {
182
+            debug("scan result in invalid state %d", state);
183
+            return;
184
+        }
185
+
186
+        bd_addr_t addr;
187
+        gap_event_advertising_report_get_address(packet, addr);
188
+
189
+        bd_addr_type_t type;
190
+        type = gap_event_advertising_report_get_address_type(packet);
191
+
192
+        int8_t rssi;
193
+        rssi = (int8_t)gap_event_advertising_report_get_rssi(packet);
194
+
195
+        // add data received so far
196
+        hci_add_scan_result(addr, type, rssi);
197
+
198
+        // get advertisement from report event
199
+        const uint8_t *adv_data = gap_event_advertising_report_get_data(packet);
200
+        uint8_t adv_len = gap_event_advertising_report_get_data_length(packet);
201
+
202
+        // iterate over advertisement data
203
+        ad_context_t context;
204
+        for (ad_iterator_init(&context, adv_len, adv_data);
205
+             ad_iterator_has_more(&context);
206
+             ad_iterator_next(&context)) {
207
+            uint8_t data_type = ad_iterator_get_data_type(&context);
208
+            uint8_t data_size = ad_iterator_get_data_len(&context);
209
+            const uint8_t *data = ad_iterator_get_data(&context);
210
+            switch (data_type) {
211
+            case BLUETOOTH_DATA_TYPE_SHORTENED_LOCAL_NAME:
212
+            case BLUETOOTH_DATA_TYPE_COMPLETE_LOCAL_NAME:
213
+                hci_scan_result_add_name(addr, data, data_size);
214
+                break;
215
+
216
+            case BLUETOOTH_DATA_TYPE_SERVICE_DATA:
217
+            case BLUETOOTH_DATA_TYPE_MANUFACTURER_SPECIFIC_DATA:
218
+                // TODO ugly
219
+                if (data_size == 12) {
220
+                    // Crafty+
221
+                    hci_scan_result_add_data(addr, data, data_size);
222
+                } else if (data_size == 26) {
223
+                    // Volcano
224
+                    hci_scan_result_add_data(addr, data, data_size);
225
+                }
226
+                break;
227
+
228
+            default:
229
+                //debug("Unexpected advertisement type 0x%02X from %s", data_type, bd_addr_to_str(addr));
230
+                //hexdump(data, data_size);
231
+                break;
232
+            }
233
+        }
234
+        break;
235
+    }
236
+
237
+    case HCI_EVENT_LE_META:
238
+        switch (hci_event_le_meta_get_subevent_code(packet)) {
239
+            case HCI_SUBEVENT_LE_CONNECTION_COMPLETE:
240
+                if (state != TC_W4_CONNECT) {
241
+                    return;
242
+                }
243
+                debug("connection complete");
244
+                connection_handle = hci_subevent_le_connection_complete_get_connection_handle(packet);
245
+                state = TC_READY;
246
+                break;
247
+
248
+            default:
249
+                //debug("unexpected LE meta event 0x%02X", hci_event_le_meta_get_subevent_code(packet));
250
+                break;
251
+        }
252
+        break;
253
+
254
+    case HCI_EVENT_DISCONNECTION_COMPLETE:
255
+        debug("disconnected");
256
+        connection_handle = HCI_CON_HANDLE_INVALID;
257
+        state = TC_IDLE;
258
+        break;
259
+
260
+    case GATT_EVENT_CHARACTERISTIC_VALUE_QUERY_RESULT:
261
+        if (state != TC_W4_READ) {
262
+            debug("gatt value query result in invalid state %d", state);
263
+            return;
264
+        }
265
+        uint16_t len = gatt_event_characteristic_value_query_result_get_value_length(packet);
266
+        if ((read_len + len) > BLE_MAX_VALUE_LEN) {
267
+            debug("not enough space for value (%d + %d > %d)", read_len, len, BLE_MAX_VALUE_LEN);
268
+            return;
269
+        }
270
+        memcpy(data_buff + read_len,
271
+               gatt_event_characteristic_value_query_result_get_value(packet),
272
+               len);
273
+        read_len += len;
274
+        break;
275
+
276
+    case GATT_EVENT_SERVICE_QUERY_RESULT:
277
+        if (state != TC_W4_SERVICE) {
278
+            debug("gatt service query result in invalid state %d", state);
279
+            return;
280
+        }
281
+        gatt_event_service_query_result_get_service(packet, &services[service_idx].service);
282
+        //debug("got service %s result", uuid128_to_str(services[service_idx].service.uuid128));
283
+        break;
284
+
285
+    case GATT_EVENT_CHARACTERISTIC_QUERY_RESULT:
286
+        if (state != TC_W4_CHARACTERISTIC) {
287
+            debug("gatt characteristic query result in invalid state %d", state);
288
+            return;
289
+        }
290
+        gatt_event_characteristic_query_result_get_characteristic(packet, &services[service_idx].chars[characteristic_idx].c);
291
+        //debug("got characteristic %s result", uuid128_to_str(services[service_idx].chars[characteristic_idx].c.uuid128));
292
+        break;
293
+
294
+    case GATT_EVENT_QUERY_COMPLETE: {
295
+        uint8_t att_status = gatt_event_query_complete_get_att_status(packet);
296
+        if (att_status != ATT_ERROR_SUCCESS){
297
+            debug("query result has ATT Error 0x%02x in %d", att_status, state);
298
+            state = TC_READY;
299
+            break;
300
+        }
301
+
302
+        switch (state) {
303
+        case TC_W4_READ:
304
+            state = TC_READ_COMPLETE;
305
+            break;
306
+
307
+        case TC_W4_SERVICE:
308
+            //debug("service %s complete", uuid128_to_str(services[service_idx].service.uuid128));
309
+            state = TC_READY;
310
+            break;
311
+
312
+        case TC_W4_CHARACTERISTIC:
313
+            //debug("characteristic %s complete", uuid128_to_str(services[service_idx].chars[characteristic_idx].c.uuid128));
314
+            state = TC_READY;
315
+            break;
316
+
317
+        case TC_W4_WRITE:
318
+            //debug("write complete");
319
+            state = TC_WRITE_COMPLETE;
320
+            break;
321
+
322
+        default:
323
+            debug("gatt query complete in invalid state %d", state);
324
+            break;
325
+        }
326
+        break;
327
+    }
328
+
329
+    default:
330
+        //debug("unexpected event 0x%02X", hci_event_packet_get_type(packet));
331
+        break;
332
+    }
333
+}
334
+
335
+void ble_init(void) {
336
+    cyw43_thread_enter();
337
+
338
+    state = TC_OFF;
339
+    for (uint i = 0; i < BLE_MAX_SCAN_RESULTS; i++) {
340
+        scans[i].set = false;
341
+    }
342
+    for (uint i = 0; i < BLE_MAX_SERVICES; i++) {
343
+        services[i].set = false;
344
+        for (uint j = 0; j < BLE_MAX_CHARACTERISTICS; j++) {
345
+            services[i].chars[j].set = false;
346
+        }
347
+    }
348
+
349
+    cyw43_thread_exit();
350
+
351
+    l2cap_init();
352
+    sm_init();
353
+    sm_set_io_capabilities(IO_CAPABILITY_NO_INPUT_NO_OUTPUT);
354
+
355
+    gatt_client_init();
356
+
357
+    hci_event_callback_registration.callback = &hci_event_handler;
358
+    hci_add_event_handler(&hci_event_callback_registration);
359
+
360
+    hci_power_control(HCI_POWER_ON);
361
+}
362
+
363
+bool ble_is_ready(void) {
364
+    cyw43_thread_enter();
365
+
366
+    bool v = (state != TC_OFF);
367
+
368
+    cyw43_thread_exit();
369
+    return v;
370
+}
371
+
372
+void ble_scan(enum ble_scan_mode mode) {
373
+    cyw43_thread_enter();
374
+
375
+    if (state == TC_OFF) {
376
+        cyw43_thread_exit();
377
+        return;
378
+    }
379
+
380
+    switch (mode) {
381
+    case BLE_SCAN_OFF:
382
+        debug("stopping BLE scan");
383
+        gap_stop_scan();
384
+        state = TC_IDLE;
385
+        break;
386
+
387
+    case BLE_SCAN_ON:
388
+        debug("starting BLE scan");
389
+        state = TC_W4_SCAN;
390
+        gap_set_scan_parameters(1, 0x0030, 0x0030);
391
+        gap_start_scan();
392
+        break;
393
+
394
+    case BLE_SCAN_TOGGLE:
395
+        switch (state) {
396
+        case TC_W4_SCAN:
397
+            cyw43_thread_exit();
398
+            ble_scan(0);
399
+            return;
400
+
401
+        case TC_IDLE:
402
+            cyw43_thread_exit();
403
+            ble_scan(1);
404
+            return;
405
+
406
+        default:
407
+            debug("invalid state %d", state);
408
+            break;
409
+        }
410
+        break;
411
+
412
+    default:
413
+        debug("invalid mode %d", mode);
414
+        break;
415
+    }
416
+
417
+    cyw43_thread_exit();
418
+}
419
+
420
+int32_t ble_get_scan_results(struct ble_scan_result *buf, uint16_t len) {
421
+    if (!buf || (len <= 0)) {
422
+        return -1;
423
+    }
424
+
425
+    cyw43_thread_enter();
426
+
427
+    if (state == TC_OFF) {
428
+        cyw43_thread_exit();
429
+        return -1;
430
+    }
431
+
432
+    uint16_t pos = 0;
433
+    for (uint16_t i = 0; i < BLE_MAX_SCAN_RESULTS; i++) {
434
+        if (!scans[i].set) {
435
+            continue;
436
+        }
437
+
438
+        // only age out entries while scanning, otherwise keep results cached
439
+        if (state == TC_W4_SCAN) {
440
+            uint32_t diff = to_ms_since_boot(get_absolute_time()) - scans[i].time;
441
+            if (diff >= BLE_MAX_SCAN_AGE_MS) {
442
+                //debug("removing %s due to age", bd_addr_to_str(scans[i].addr));
443
+                scans[i].set = false;
444
+            }
445
+        }
446
+
447
+        memcpy(buf + pos, scans + i, sizeof(struct ble_scan_result));
448
+        pos++;
449
+
450
+        if (pos >= len) {
451
+            break;
452
+        }
453
+    }
454
+
455
+    cyw43_thread_exit();
456
+    return pos;
457
+}
458
+
459
+void ble_connect(bd_addr_t addr, bd_addr_type_t type) {
460
+    cyw43_thread_enter();
461
+
462
+    switch (state) {
463
+    case TC_W4_SCAN:
464
+        cyw43_thread_exit();
465
+        ble_scan(0);
466
+        cyw43_thread_enter();
467
+        break;
468
+
469
+    case TC_READY:
470
+        gap_disconnect(connection_handle);
471
+        break;
472
+
473
+    case TC_IDLE:
474
+        break;
475
+
476
+    default:
477
+        debug("invalid state for connect %d", state);
478
+        cyw43_thread_exit();
479
+        return;
480
+    }
481
+
482
+    debug("connecting to %s", bd_addr_to_str(addr));
483
+    state = TC_W4_CONNECT;
484
+    gap_connect(addr, type);
485
+
486
+    cyw43_thread_exit();
487
+}
488
+
489
+bool ble_is_connected(void) {
490
+    cyw43_thread_enter();
491
+
492
+    bool v = (state == TC_READY)
493
+             || (state == TC_W4_READ)
494
+             || (state == TC_READ_COMPLETE)
495
+             || (state == TC_W4_SERVICE)
496
+             || (state == TC_W4_CHARACTERISTIC)
497
+             || (state == TC_W4_WRITE)
498
+             || (state == TC_WRITE_COMPLETE);
499
+
500
+    cyw43_thread_exit();
501
+    return v;
502
+}
503
+
504
+void ble_disconnect(void) {
505
+    cyw43_thread_enter();
506
+
507
+    if (state == TC_READY) {
508
+        debug("disconnecting");
509
+        gap_disconnect(connection_handle);
510
+    } else {
511
+        debug("invalid state for disconnect %d", state);
512
+    }
513
+
514
+    cyw43_thread_exit();
515
+}
516
+
517
+int32_t ble_read(const uint8_t *characteristic, uint8_t *buff, uint16_t buff_len) {
518
+    cyw43_thread_enter();
519
+
520
+    if (state != TC_READY) {
521
+        cyw43_thread_exit();
522
+        debug("invalid state for read (%d)", state);
523
+        return -1;
524
+    }
525
+
526
+    uint8_t r = gatt_client_read_value_of_characteristics_by_uuid128(hci_event_handler,
527
+                                                                     connection_handle,
528
+                                                                     0x0001, 0xFFFF,
529
+                                                                     characteristic);
530
+    if (r != ERROR_CODE_SUCCESS) {
531
+        cyw43_thread_exit();
532
+        debug("gatt read failed %d", r);
533
+        return -2;
534
+    }
535
+
536
+    state = TC_W4_READ;
537
+    read_len = 0;
538
+    cyw43_thread_exit();
539
+
540
+    uint32_t start_time = to_ms_since_boot(get_absolute_time());
541
+    while (1) {
542
+        sleep_ms(1);
543
+        main_loop_hw();
544
+
545
+        uint32_t now = to_ms_since_boot(get_absolute_time());
546
+        if ((now - start_time) >= BLE_READ_TIMEOUT_MS) {
547
+            debug("timeout waiting for read");
548
+            cyw43_thread_enter();
549
+            state = TC_READY;
550
+            cyw43_thread_exit();
551
+            return -3;
552
+        }
553
+
554
+        cyw43_thread_enter();
555
+        enum ble_state state_cached = state;
556
+        cyw43_thread_exit();
557
+
558
+        if (state_cached == TC_READ_COMPLETE) {
559
+            break;
560
+        }
561
+    }
562
+
563
+    cyw43_thread_enter();
564
+
565
+    state = TC_READY;
566
+
567
+    if (read_len > buff_len) {
568
+        debug("buffer too short (%d < %d)", buff_len, read_len);
569
+        cyw43_thread_exit();
570
+        return -4;
571
+    }
572
+
573
+    memcpy(buff, data_buff, read_len);
574
+
575
+    cyw43_thread_exit();
576
+    return read_len;
577
+}
578
+
579
+int8_t ble_write(const uint8_t *service, const uint8_t *characteristic,
580
+                  uint8_t *buff, uint16_t buff_len) {
581
+    cyw43_thread_enter();
582
+
583
+    if (state != TC_READY) {
584
+        cyw43_thread_exit();
585
+        debug("invalid state for write (%d)", state);
586
+        return -1;
587
+    }
588
+
589
+    // check if service has already been discovered
590
+    int srvc = -1, free_srvc = -1;
591
+    for (int i = 0; i < BLE_MAX_SERVICES; i++) {
592
+        if (!services[i].set) {
593
+            if (free_srvc < 0) {
594
+                free_srvc = i;
595
+            }
596
+            continue;
597
+        }
598
+
599
+        if (memcmp(services[i].service.uuid128, service, 16) == 0) {
600
+            srvc = i;
601
+            break;
602
+        }
603
+    }
604
+
605
+    // if this service has not been discovered yet, add it
606
+    if (srvc < 0) {
607
+        if (free_srvc < 0) {
608
+            debug("no space left for BLE service. overwriting.");
609
+            free_srvc = 0;
610
+        }
611
+        srvc = free_srvc;
612
+        services[srvc].set = true;
613
+
614
+        debug("discovering service %s at %d", uuid128_to_str(service), srvc);
615
+
616
+        uint8_t r = gatt_client_discover_primary_services_by_uuid128(hci_event_handler,
617
+                                                                     connection_handle,
618
+                                                                     service);
619
+        if (r != ERROR_CODE_SUCCESS) {
620
+            cyw43_thread_exit();
621
+            debug("gatt service discovery failed %d", r);
622
+            return -2;
623
+        }
624
+
625
+        state = TC_W4_SERVICE;
626
+        service_idx = srvc;
627
+        cyw43_thread_exit();
628
+
629
+        uint32_t start_time = to_ms_since_boot(get_absolute_time());
630
+        while (1) {
631
+            sleep_ms(1);
632
+            main_loop_hw();
633
+
634
+            uint32_t now = to_ms_since_boot(get_absolute_time());
635
+            if ((now - start_time) >= BLE_SRVC_TIMEOUT_MS) {
636
+                debug("timeout waiting for service");
637
+                cyw43_thread_enter();
638
+                state = TC_READY;
639
+                cyw43_thread_exit();
640
+                return -3;
641
+            }
642
+
643
+            cyw43_thread_enter();
644
+            enum ble_state state_cached = state;
645
+            cyw43_thread_exit();
646
+
647
+            if (state_cached == TC_READY) {
648
+                break;
649
+            }
650
+        }
651
+
652
+        cyw43_thread_enter();
653
+    }
654
+
655
+    // check if characteristic has already been discovered
656
+    int ch = -1, free_ch = -1;
657
+    for (int i = 0; i < BLE_MAX_CHARACTERISTICS; i++) {
658
+        if (!services[srvc].chars[i].set) {
659
+            if (free_ch < 0) {
660
+                free_ch = i;
661
+            }
662
+            continue;
663
+        }
664
+
665
+        if (memcmp(services[srvc].chars[i].c.uuid128, characteristic, 16) == 0) {
666
+            ch = i;
667
+            break;
668
+        }
669
+    }
670
+
671
+    // if this characteristic has not been discovered yet, add it
672
+    if (ch < 0) {
673
+        if (free_ch < 0) {
674
+            debug("no space left for BLE characteristic. overwriting.");
675
+            free_ch = 0;
676
+        }
677
+        ch = free_ch;
678
+        services[srvc].chars[ch].set = true;
679
+
680
+        debug("discovering characteristic %s at %d", uuid128_to_str(characteristic), ch);
681
+
682
+        uint8_t r = gatt_client_discover_characteristics_for_service_by_uuid128(hci_event_handler,
683
+                                                                                connection_handle,
684
+                                                                                &services[srvc].service,
685
+                                                                                characteristic);
686
+        if (r != ERROR_CODE_SUCCESS) {
687
+            cyw43_thread_exit();
688
+            debug("gatt characteristic discovery failed %d", r);
689
+            return -4;
690
+        }
691
+
692
+        state = TC_W4_CHARACTERISTIC;
693
+        characteristic_idx = ch;
694
+        cyw43_thread_exit();
695
+
696
+        uint32_t start_time = to_ms_since_boot(get_absolute_time());
697
+        while (1) {
698
+            sleep_ms(1);
699
+            main_loop_hw();
700
+
701
+            uint32_t now = to_ms_since_boot(get_absolute_time());
702
+            if ((now - start_time) >= BLE_CHAR_TIMEOUT_MS) {
703
+                debug("timeout waiting for characteristic");
704
+                cyw43_thread_enter();
705
+                state = TC_READY;
706
+                cyw43_thread_exit();
707
+                return -5;
708
+            }
709
+
710
+            cyw43_thread_enter();
711
+            enum ble_state state_cached = state;
712
+            cyw43_thread_exit();
713
+
714
+            if (state_cached == TC_READY) {
715
+                break;
716
+            }
717
+        }
718
+
719
+        cyw43_thread_enter();
720
+    }
721
+
722
+    if (buff_len > BLE_MAX_VALUE_LEN) {
723
+        buff_len = BLE_MAX_VALUE_LEN;
724
+    }
725
+    memcpy(data_buff, buff, buff_len);
726
+
727
+    uint8_t r = gatt_client_write_value_of_characteristic(hci_event_handler,
728
+                                                          connection_handle,
729
+                                                          services[srvc].chars[ch].c.value_handle,
730
+                                                          buff_len, data_buff);
731
+    if (r != ERROR_CODE_SUCCESS) {
732
+        cyw43_thread_exit();
733
+        debug("gatt write failed %d", r);
734
+        return -6;
735
+    }
736
+
737
+    state = TC_W4_WRITE;
738
+    cyw43_thread_exit();
739
+
740
+    uint32_t start_time = to_ms_since_boot(get_absolute_time());
741
+    while (1) {
742
+        sleep_ms(1);
743
+        main_loop_hw();
744
+
745
+        uint32_t now = to_ms_since_boot(get_absolute_time());
746
+        if ((now - start_time) >= BLE_WRTE_TIMEOUT_MS) {
747
+            debug("timeout waiting for write");
748
+            cyw43_thread_enter();
749
+            state = TC_READY;
750
+            cyw43_thread_exit();
751
+            return -7;
752
+        }
753
+
754
+        cyw43_thread_enter();
755
+        enum ble_state state_cached = state;
756
+        cyw43_thread_exit();
757
+
758
+        if ((state_cached == TC_WRITE_COMPLETE) || (state_cached == TC_READY)) {
759
+            break;
760
+        }
761
+    }
762
+
763
+    cyw43_thread_enter();
764
+
765
+    int8_t ret = (state == TC_WRITE_COMPLETE) ? 0 : -8;
766
+    state = TC_READY;
767
+
768
+    cyw43_thread_exit();
769
+    return ret;
770
+}

+ 80
- 0
src/buttons.c Bestand weergeven

@@ -0,0 +1,80 @@
1
+/*
2
+ * buttons.c
3
+ *
4
+ * Copyright (c) 2022 - 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#include "pico/stdlib.h"
20
+
21
+#include "config.h"
22
+#include "buttons.h"
23
+
24
+static const uint gpio_num[NUM_BTNS] = {
25
+    15, // BTN_A
26
+    17, // BTN_B
27
+    19, // BTN_X
28
+    21, // BTN_Y
29
+     2, // BTN_UP
30
+    18, // BTN_DOWN
31
+    16, // BTN_LEFT
32
+    20, // BTN_RIGHT
33
+     3, // BTN_ENTER
34
+};
35
+
36
+struct button_state {
37
+    uint32_t last_time;
38
+    bool current_state, last_state;
39
+};
40
+
41
+static struct button_state buttons[NUM_BTNS];
42
+static void (*callback)(enum buttons, bool) = NULL;
43
+
44
+void buttons_init(void) {
45
+    for (uint i = 0; i < NUM_BTNS; i++) {
46
+        gpio_init(gpio_num[i]);
47
+        gpio_set_dir(gpio_num[i], GPIO_IN);
48
+        gpio_pull_up(gpio_num[i]);
49
+
50
+        buttons[i].last_time = 0;
51
+        buttons[i].current_state = false;
52
+        buttons[i].last_state = false;
53
+    }
54
+}
55
+
56
+void buttons_callback(void (*fp)(enum buttons, bool)) {
57
+    callback = fp;
58
+}
59
+
60
+void buttons_run(void) {
61
+    for (uint i = 0; i < NUM_BTNS; i++) {
62
+        bool state = !gpio_get(gpio_num[i]);
63
+        uint32_t now = to_ms_since_boot(get_absolute_time());
64
+
65
+        if (state != buttons[i].last_state) {
66
+            buttons[i].last_time = now;
67
+        }
68
+
69
+        if ((now - buttons[i].last_time) > DEBOUNCE_DELAY_MS) {
70
+            if (state != buttons[i].current_state) {
71
+                buttons[i].current_state = state;
72
+                if (callback) {
73
+                    callback(i, state);
74
+                }
75
+            }
76
+        }
77
+
78
+        buttons[i].last_state = state;
79
+    }
80
+}

+ 552
- 0
src/console.c Bestand weergeven

@@ -0,0 +1,552 @@
1
+/*
2
+ * console.c
3
+ *
4
+ * Copyright (c) 2022 - 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#include <inttypes.h>
20
+#include <string.h>
21
+#include <unistd.h>
22
+#include <stdio.h>
23
+
24
+#include "pico/stdlib.h"
25
+#include "hardware/watchdog.h"
26
+
27
+#include "config.h"
28
+#include "log.h"
29
+#include "util.h"
30
+#include "usb_cdc.h"
31
+#include "usb_msc.h"
32
+#include "debug_disk.h"
33
+#include "lipo.h"
34
+#include "ble.h"
35
+#include "text.h"
36
+#include "lcd.h"
37
+#include "image.h"
38
+#include "volcano.h"
39
+#include "serial.h"
40
+#include "main.h"
41
+#include "models.h"
42
+#include "workflow.h"
43
+#include "console.h"
44
+#include "crafty.h"
45
+
46
+#define CNSL_BUFF_SIZE 64
47
+#define CNSL_REPEAT_MS 500
48
+
49
+#define DEV_AUTO_CONNECT(s) {                                     \
50
+    ble_scan(BLE_SCAN_OFF);                                       \
51
+    bd_addr_t addr;                                               \
52
+    bd_addr_type_t type;                                          \
53
+    const char *foo = s;                                          \
54
+    sscanf(foo, "%02hhX:%02hhX:%02hhX:%02hhX:%02hhX:%02hhX %hhu", \
55
+            &addr[0], &addr[1], &addr[2], &addr[3],               \
56
+            &addr[4], &addr[5], &type);                           \
57
+    ble_connect(addr, type);                                      \
58
+    while (!ble_is_connected()) {                                 \
59
+        sleep_ms(1);                                              \
60
+    }                                                             \
61
+}
62
+
63
+static char cnsl_line_buff[CNSL_BUFF_SIZE + 1];
64
+static uint32_t cnsl_buff_pos = 0;
65
+
66
+static char cnsl_last_command[CNSL_BUFF_SIZE + 1];
67
+
68
+static char cnsl_repeated_command[CNSL_BUFF_SIZE + 1];
69
+static bool repeat_command = false;
70
+static uint32_t last_repeat_time = 0;
71
+
72
+static void cnsl_interpret(const char *line) {
73
+    if (strlen(line) == 0) {
74
+        if ((strlen(cnsl_last_command) > 0) && (strcmp(cnsl_last_command, "repeat") != 0)) {
75
+            // repeat last command once
76
+            println("repeating command \"%s\"", cnsl_last_command);
77
+            cnsl_interpret(cnsl_last_command);
78
+            println();
79
+        }
80
+        return;
81
+    } else if (strcmp(line, "repeat") == 0) {
82
+        if (!repeat_command) {
83
+            // mark last command to be repeated multiple times
84
+            strncpy(cnsl_repeated_command, cnsl_last_command, CNSL_BUFF_SIZE + 1);
85
+            last_repeat_time = to_ms_since_boot(get_absolute_time()) - 1001;
86
+            repeat_command = true;
87
+        } else {
88
+            // stop repeating
89
+            repeat_command = false;
90
+        }
91
+    } else if ((strcmp(line, "help") == 0)
92
+            || (strcmp(line, "h") == 0)
93
+            || (strcmp(line, "?") == 0)) {
94
+        println("VolcanoRC Firmware Usage:");
95
+        println("");
96
+        println("  reset - reset back into this firmware");
97
+        println("   \\x18 - reset to bootloader");
98
+        println(" repeat - repeat last command every %d milliseconds", CNSL_REPEAT_MS);
99
+        println("   help - print this message");
100
+        println("  mount - make mass storage medium (un)available");
101
+        println("  power - show Lipo battery status");
102
+        println("");
103
+        println("   scan - start or stop BLE scan");
104
+        println("scanres - print list of found BLE devices");
105
+        println("con M T - connect to (M)AC and (T)ype");
106
+        println(" discon - disconnect from BLE device");
107
+        println("");
108
+        println("  clear - blank screen");
109
+        println(" splash - draw image on screen");
110
+        println("  fonts - show font list");
111
+        println("   text - draw text on screen");
112
+        println("    bat - draw battery indicator");
113
+        println("");
114
+        println("   vrct - Volcano read current temperature");
115
+        println("   vrtt - Volcano read target temperature");
116
+        println(" vwtt X - Volcano write target temperature");
117
+        println("  vwh X - Set heater to 1 or 0");
118
+        println("  vwp X - Set pump to 1 or 0");
119
+        println("");
120
+        println("    wfl - List available workflows");
121
+        println("   wf X - Run workflow");
122
+        println("");
123
+        println("   crct - Crafty read current temperature");
124
+        println("   crtt - Crafty read target temperature");
125
+        println(" cwtt X - Crafty write target temperature");
126
+        println("  cwh X - Set heater to 1 or 0");
127
+        println("    crb - Crafty read battery state");
128
+        println("");
129
+        println("Press Enter with no input to repeat last command.");
130
+        println("Use repeat to continuously execute last command.");
131
+        println("Stop this by calling repeat again.");
132
+    } else if (strcmp(line, "reset") == 0) {
133
+        reset_to_main();
134
+    } else if (strcmp(line, "mount") == 0) {
135
+        bool state = msc_is_medium_available();
136
+        println("Currently %s. %s now.",
137
+                state ? "mounted" : "unmounted",
138
+                state ? "Unplugging" : "Plugging in");
139
+        msc_set_medium_available(!state);
140
+    } else if (strcmp(line, "power") == 0) {
141
+        float volt = lipo_voltage();
142
+        println("Battery: %.2fV = %.1f%% @ %s",
143
+                volt, lipo_percentage(volt),
144
+                lipo_charging() ? "charging" : "draining");
145
+    } else if (strcmp(line, "scan") == 0) {
146
+        ble_scan(BLE_SCAN_TOGGLE);
147
+    } else if (strcmp(line, "scanres") == 0) {
148
+        struct ble_scan_result results[BLE_MAX_SCAN_RESULTS] = {0};
149
+        int n = ble_get_scan_results(results, BLE_MAX_SCAN_RESULTS);
150
+        if (n < 0) {
151
+            println("Error reading results (%d)", n);
152
+        } else {
153
+            println("%d results", n);
154
+            for (int i = 0; i < n; i++) {
155
+                char info[32] = "";
156
+                enum known_devices dev = models_filter_name(results[i].name);
157
+                if (dev != DEV_UNKNOWN) {
158
+                    models_get_serial(results[i].data, results[i].data_len,
159
+                                      info, sizeof(info));
160
+                }
161
+                uint32_t age = to_ms_since_boot(get_absolute_time()) - results[i].time;
162
+                println("addr=%s type=%d rssi=%d age=%.1fs name='%s' info='%s'",
163
+                        bd_addr_to_str(results[i].addr),
164
+                        results[i].type, results[i].rssi,
165
+                        age / 1000.0, results[i].name, info);
166
+            }
167
+        }
168
+    } else if (str_startswith(line, "con ")) {
169
+        bd_addr_t addr;
170
+        bd_addr_type_t type;
171
+        int r = sscanf(line, "con %02hhX:%02hhX:%02hhX:%02hhX:%02hhX:%02hhX %hhu",
172
+                       &addr[0], &addr[1], &addr[2], &addr[3],
173
+                       &addr[4], &addr[5], &type);
174
+
175
+        if (r == 7) {
176
+            debug("connecting");
177
+            ble_connect(addr, type);
178
+        } else {
179
+            debug("invalid input (%d)", r);
180
+        }
181
+    } else if (strcmp(line, "discon") == 0) {
182
+        ble_disconnect();
183
+    } else if (strcmp(line, "clear") == 0) {
184
+        lcd_clear();
185
+    } else if (strcmp(line, "splash") == 0) {
186
+        draw_splash();
187
+    } else if (strcmp(line, "fonts") == 0) {
188
+        const struct mf_font_list_s *f = mf_get_font_list();
189
+
190
+        debug("Font list:");
191
+        while (f) {
192
+            debug("full_name: %s", f->font->full_name);
193
+            debug("short_name: %s", f->font->short_name);
194
+            debug("size: %d %d", f->font->width, f->font->height);
195
+            debug("x_advance: %d %d", f->font->min_x_advance, f->font->max_x_advance);
196
+            debug("baseline: %d %d", f->font->baseline_x, f->font->baseline_y);
197
+            debug("line_height: %d", f->font->line_height);
198
+            debug("flags: %d", f->font->flags);
199
+            debug("fallback_character: %c", f->font->fallback_character);
200
+            debug("character_width: %p", f->font->character_width);
201
+            debug("render_character: %p", f->font->render_character);
202
+
203
+            f = f->next;
204
+            if (f) {
205
+                debug("");
206
+            }
207
+        }
208
+    } else if (strcmp(line, "text") == 0) {
209
+        uint16_t y_off = 0;
210
+        const struct mf_font_list_s *f = mf_get_font_list();
211
+        while (f) {
212
+            struct text_font font = {
213
+                .fontname = f->font->short_name,
214
+                //.scale = 1,
215
+                .font = f->font,
216
+            };
217
+            text_prepare_font(&font);
218
+
219
+            struct text_conf text = {
220
+                .text = font.fontname,
221
+                .x = 0,
222
+                .y = y_off,
223
+                .justify = false,
224
+                .alignment = MF_ALIGN_CENTER,
225
+                .width = 240,
226
+                .height = 240 - y_off,
227
+                .margin = 5,
228
+                .bg = TEXT_BG_NONE,
229
+                .font = &font,
230
+            };
231
+            text_draw(&text);
232
+
233
+            y_off = text.y;
234
+            f = f->next;
235
+        }
236
+    } else if (strcmp(line, "bat") == 0) {
237
+        draw_battery_indicator();
238
+    } else if (strcmp(line, "vrct") == 0) {
239
+#ifdef TEST_VOLCANO_AUTO_CONNECT
240
+        DEV_AUTO_CONNECT(TEST_VOLCANO_AUTO_CONNECT);
241
+#endif // TEST_VOLCANO_AUTO_CONNECT
242
+
243
+        int16_t r = volcano_get_current_temp();
244
+        println("volcano current temp: %.1f", r / 10.0);
245
+
246
+#ifdef TEST_VOLCANO_AUTO_CONNECT
247
+        ble_disconnect();
248
+#endif // TEST_VOLCANO_AUTO_CONNECT
249
+    } else if (strcmp(line, "vrtt") == 0) {
250
+#ifdef TEST_VOLCANO_AUTO_CONNECT
251
+        DEV_AUTO_CONNECT(TEST_VOLCANO_AUTO_CONNECT);
252
+#endif // TEST_VOLCANO_AUTO_CONNECT
253
+
254
+        int16_t r = volcano_get_target_temp();
255
+        println("volcano target temp: %.1f", r / 10.0);
256
+
257
+#ifdef TEST_VOLCANO_AUTO_CONNECT
258
+        ble_disconnect();
259
+#endif // TEST_VOLCANO_AUTO_CONNECT
260
+    } else if (str_startswith(line, "vwtt ")) {
261
+        float val;
262
+        int r = sscanf(line, "vwtt %f", &val);
263
+        if (r != 1) {
264
+            println("invalid input (%d)", r);
265
+        } else {
266
+            uint16_t v = val * 10.0;
267
+
268
+#ifdef TEST_VOLCANO_AUTO_CONNECT
269
+            DEV_AUTO_CONNECT(TEST_VOLCANO_AUTO_CONNECT);
270
+#endif // TEST_VOLCANO_AUTO_CONNECT
271
+
272
+            int8_t r = volcano_set_target_temp(v);
273
+
274
+#ifdef TEST_VOLCANO_AUTO_CONNECT
275
+            ble_disconnect();
276
+#endif // TEST_VOLCANO_AUTO_CONNECT
277
+
278
+            if (r < 0) {
279
+                println("error writing target temp %d", r);
280
+            } else {
281
+                println("success");
282
+            }
283
+        }
284
+    } else if (str_startswith(line, "vwh ")) {
285
+        int val;
286
+        int r = sscanf(line, "vwh %d", &val);
287
+        if ((r != 1) || ((val != 0) && (val != 1))) {
288
+            println("invalid input (%d %d)", r, val);
289
+        } else {
290
+#ifdef TEST_VOLCANO_AUTO_CONNECT
291
+            DEV_AUTO_CONNECT(TEST_VOLCANO_AUTO_CONNECT);
292
+#endif // TEST_VOLCANO_AUTO_CONNECT
293
+
294
+            int8_t r = volcano_set_heater_state(val == 1);
295
+
296
+#ifdef TEST_VOLCANO_AUTO_CONNECT
297
+            ble_disconnect();
298
+#endif // TEST_VOLCANO_AUTO_CONNECT
299
+
300
+            if (r < 0) {
301
+                println("error writing heater state %d", r);
302
+            } else {
303
+                println("success");
304
+            }
305
+        }
306
+    } else if (str_startswith(line, "vwp ")) {
307
+        int val;
308
+        int r = sscanf(line, "vwp %d", &val);
309
+        if ((r != 1) || ((val != 0) && (val != 1))) {
310
+            println("invalid input (%d %d)", r, val);
311
+        } else {
312
+#ifdef TEST_VOLCANO_AUTO_CONNECT
313
+            DEV_AUTO_CONNECT(TEST_VOLCANO_AUTO_CONNECT);
314
+#endif // TEST_VOLCANO_AUTO_CONNECT
315
+
316
+            int8_t r = volcano_set_pump_state(val == 1);
317
+
318
+#ifdef TEST_VOLCANO_AUTO_CONNECT
319
+            ble_disconnect();
320
+#endif // TEST_VOLCANO_AUTO_CONNECT
321
+
322
+            if (r < 0) {
323
+                println("error writing pump state %d", r);
324
+            } else {
325
+                println("success");
326
+            }
327
+        }
328
+    } else if (strcmp(line, "wfl") == 0) {
329
+        println("%d workflows", wf_count());
330
+        for (int i = 0; i < wf_count(); i++) {
331
+            println("  '%s' by %s", wf_name(i), wf_author(i));
332
+        }
333
+    } else if (str_startswith(line, "wf ")) {
334
+        int wf = -1;
335
+        for (int i = 0; i < wf_count(); i++) {
336
+            if (strcmp(wf_name(i), line + 3) == 0) {
337
+                wf = i;
338
+                break;
339
+            }
340
+        }
341
+
342
+        if (wf < 0) {
343
+            println("unknown workflow");
344
+        } else {
345
+            struct wf_state s = wf_status();
346
+            if (s.status != WF_IDLE) {
347
+                println("workflow in progress");
348
+            } else {
349
+#ifdef TEST_VOLCANO_AUTO_CONNECT
350
+                DEV_AUTO_CONNECT(TEST_VOLCANO_AUTO_CONNECT);
351
+#endif // TEST_VOLCANO_AUTO_CONNECT
352
+
353
+                println("starting workflow");
354
+                wf_start(wf);
355
+
356
+                s = wf_status();
357
+                while (s.status != WF_IDLE) {
358
+                    main_loop_hw();
359
+                    wf_run();
360
+                    s = wf_status();
361
+                }
362
+
363
+                println("done");
364
+
365
+#ifdef TEST_VOLCANO_AUTO_CONNECT
366
+                ble_disconnect();
367
+#endif // TEST_VOLCANO_AUTO_CONNECT
368
+            }
369
+        }
370
+    } else if (strcmp(line, "crct") == 0) {
371
+#ifdef TEST_CRAFTY_AUTO_CONNECT
372
+        DEV_AUTO_CONNECT(TEST_CRAFTY_AUTO_CONNECT);
373
+#endif // TEST_CRAFTY_AUTO_CONNECT
374
+
375
+        int16_t r = crafty_get_current_temp();
376
+        println("crafty current temp: %.1f", r / 10.0);
377
+
378
+#ifdef TEST_CRAFTY_AUTO_CONNECT
379
+        ble_disconnect();
380
+#endif // TEST_CRAFTY_AUTO_CONNECT
381
+    } else if (strcmp(line, "crtt") == 0) {
382
+#ifdef TEST_CRAFTY_AUTO_CONNECT
383
+        DEV_AUTO_CONNECT(TEST_CRAFTY_AUTO_CONNECT);
384
+#endif // TEST_CRAFTY_AUTO_CONNECT
385
+
386
+        int16_t r = crafty_get_target_temp();
387
+        println("crafty target temp: %.1f", r / 10.0);
388
+
389
+#ifdef TEST_CRAFTY_AUTO_CONNECT
390
+        ble_disconnect();
391
+#endif // TEST_CRAFTY_AUTO_CONNECT
392
+    } else if (str_startswith(line, "cwtt ")) {
393
+        float val;
394
+        int r = sscanf(line, "cwtt %f", &val);
395
+        if (r != 1) {
396
+            println("invalid input (%d)", r);
397
+        } else {
398
+            uint16_t v = val * 10.0;
399
+
400
+#ifdef TEST_CRAFTY_AUTO_CONNECT
401
+            DEV_AUTO_CONNECT(TEST_CRAFTY_AUTO_CONNECT);
402
+#endif // TEST_CRAFTY_AUTO_CONNECT
403
+
404
+            int8_t r = crafty_set_target_temp(v);
405
+
406
+#ifdef TEST_CRAFTY_AUTO_CONNECT
407
+            ble_disconnect();
408
+#endif // TEST_CRAFTY_AUTO_CONNECT
409
+
410
+            if (r < 0) {
411
+                println("error writing target temp %d", r);
412
+            } else {
413
+                println("success");
414
+            }
415
+        }
416
+    } else if (str_startswith(line, "cwh ")) {
417
+        int val;
418
+        int r = sscanf(line, "cwh %d", &val);
419
+        if ((r != 1) || ((val != 0) && (val != 1))) {
420
+            println("invalid input (%d %d)", r, val);
421
+        } else {
422
+#ifdef TEST_CRAFTY_AUTO_CONNECT
423
+            DEV_AUTO_CONNECT(TEST_CRAFTY_AUTO_CONNECT);
424
+#endif // TEST_CRAFTY_AUTO_CONNECT
425
+
426
+            int8_t r = crafty_set_heater_state(val == 1);
427
+
428
+#ifdef TEST_CRAFTY_AUTO_CONNECT
429
+            ble_disconnect();
430
+#endif // TEST_CRAFTY_AUTO_CONNECT
431
+
432
+            if (r < 0) {
433
+                println("error writing heater state %d", r);
434
+            } else {
435
+                println("success");
436
+            }
437
+        }
438
+    } else if (strcmp(line, "crb") == 0) {
439
+#ifdef TEST_CRAFTY_AUTO_CONNECT
440
+        DEV_AUTO_CONNECT(TEST_CRAFTY_AUTO_CONNECT);
441
+#endif // TEST_CRAFTY_AUTO_CONNECT
442
+
443
+        int16_t r = crafty_get_battery_state();
444
+        println("crafty battery: %d %%", r);
445
+
446
+#ifdef TEST_CRAFTY_AUTO_CONNECT
447
+        ble_disconnect();
448
+#endif // TEST_CRAFTY_AUTO_CONNECT
449
+    } else {
450
+        println("unknown command \"%s\"", line);
451
+    }
452
+
453
+    println();
454
+}
455
+
456
+void cnsl_init(void) {
457
+    cnsl_buff_pos = 0;
458
+    for (int i = 0; i < CNSL_BUFF_SIZE + 1; i++) {
459
+        cnsl_line_buff[i] = '\0';
460
+        cnsl_last_command[i] = '\0';
461
+        cnsl_repeated_command[i] = '\0';
462
+    }
463
+}
464
+
465
+static int32_t cnsl_find_line_end(void) {
466
+    for (uint32_t i = 0; i < cnsl_buff_pos; i++) {
467
+        if ((cnsl_line_buff[i] == '\r') || (cnsl_line_buff[i] == '\n')) {
468
+            return i;
469
+        }
470
+    }
471
+    return -1;
472
+}
473
+
474
+void cnsl_run(void) {
475
+    if (repeat_command && (strlen(cnsl_repeated_command) > 0)
476
+            && (strcmp(cnsl_repeated_command, "repeat") != 0)) {
477
+        uint32_t now = to_ms_since_boot(get_absolute_time());
478
+        if (now >= (last_repeat_time + CNSL_REPEAT_MS)) {
479
+            println("repeating command \"%s\"", cnsl_repeated_command);
480
+            cnsl_interpret(cnsl_repeated_command);
481
+            println();
482
+
483
+            last_repeat_time = now;
484
+        }
485
+    } else {
486
+        if (repeat_command) {
487
+            println("nothing to repeat");
488
+        }
489
+        repeat_command = false;
490
+    }
491
+}
492
+
493
+void cnsl_handle_input(const uint8_t *buf, size_t len) {
494
+    if ((cnsl_buff_pos + len) > CNSL_BUFF_SIZE) {
495
+        debug("error: console input buffer overflow! %lu > %u", cnsl_buff_pos + len, CNSL_BUFF_SIZE);
496
+        cnsl_init();
497
+    }
498
+
499
+    memcpy(cnsl_line_buff + cnsl_buff_pos, buf, len);
500
+    cnsl_buff_pos += len;
501
+
502
+    // handle backspace and local echo
503
+    for (ssize_t i = cnsl_buff_pos - len; i < (ssize_t)cnsl_buff_pos; i++) {
504
+        if ((cnsl_line_buff[i] == '\b') || (cnsl_line_buff[i] == 0x7F)) {
505
+            if (i > 0) {
506
+                // overwrite previous character and backspace
507
+                for (ssize_t j = i; j < (ssize_t)cnsl_buff_pos - 1; j++) {
508
+                    cnsl_line_buff[j - 1] = cnsl_line_buff[j + 1];
509
+                }
510
+                cnsl_buff_pos -= 2;
511
+            } else {
512
+                // just remove the backspace
513
+                for (ssize_t j = i; j < (ssize_t)cnsl_buff_pos - 1; j++) {
514
+                    cnsl_line_buff[j] = cnsl_line_buff[j + 1];
515
+                }
516
+                cnsl_buff_pos -= 1;
517
+            }
518
+
519
+            usb_cdc_write((const uint8_t *)"\b \b", 3);
520
+            serial_write((const uint8_t *)"\b \b", 3);
521
+
522
+            // check for another backspace in this space
523
+            i--;
524
+        } else {
525
+            usb_cdc_write((const uint8_t *)(cnsl_line_buff + i), 1);
526
+            serial_write((const uint8_t *)(cnsl_line_buff + i), 1);
527
+        }
528
+    }
529
+
530
+    int32_t line_len = cnsl_find_line_end();
531
+    if (line_len < 0) {
532
+        // user has not pressed enter yet
533
+        return;
534
+    }
535
+
536
+    // convert line to C-style string
537
+    cnsl_line_buff[line_len] = '\0';
538
+
539
+    cnsl_interpret(cnsl_line_buff);
540
+
541
+    // store command for eventual repeats
542
+    strncpy(cnsl_last_command, cnsl_line_buff, CNSL_BUFF_SIZE + 1);
543
+
544
+    // clear string and move following data over
545
+    uint32_t cnt = line_len + 1;
546
+    if (cnsl_line_buff[line_len + 1] == '\n') {
547
+        cnt++;
548
+    }
549
+    memset(cnsl_line_buff, '\0', cnt);
550
+    memmove(cnsl_line_buff, cnsl_line_buff + cnt, sizeof(cnsl_line_buff) - cnt);
551
+    cnsl_buff_pos -= cnt;
552
+}

+ 121
- 0
src/crafty.c Bestand weergeven

@@ -0,0 +1,121 @@
1
+/*
2
+ * crafty.c
3
+ *
4
+ * Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#include "config.h"
20
+#include "log.h"
21
+#include "ble.h"
22
+#include "crafty.h"
23
+
24
+// serviceUuidCrafty1      "00000001-4c45-4b43-4942-265a524f5453"
25
+// characteristicWriteTemp "00000021-4c45-4b43-4942-265a524f5453"
26
+// charactersiticCurrTemp  "00000011-4c45-4b43-4942-265a524f5453"
27
+// characteristicPower     "00000041-4c45-4b43-4942-265a524f5453"
28
+// characteristicHeaterOn  "00000081-4c45-4b43-4942-265a524f5453"
29
+// characteristicHeaterOff "00000091-4c45-4b43-4942-265a524f5453"
30
+
31
+// "000000xx-4c45-4b43-4942-265a524f5453"
32
+static uint8_t uuid_base[16] = {
33
+    0x00, 0x00, 0x00, 0xFF, 0x4c, 0x45, 0x4b, 0x43,
34
+    0x49, 0x42, 0x26, 0x5a, 0x52, 0x4f, 0x54, 0x53,
35
+};
36
+static uint8_t uuid_base2[16] = {
37
+    0x00, 0x00, 0x00, 0xFF, 0x4c, 0x45, 0x4b, 0x43,
38
+    0x49, 0x42, 0x26, 0x5a, 0x52, 0x4f, 0x54, 0x53,
39
+};
40
+
41
+// Crafty UUIDs are always the same, except for the 4th byte
42
+#define UUID_WRITE_SRVC   0x01
43
+#define UUID_CURRENT_TEMP 0x11
44
+#define UUID_TARGET_TEMP  0x21
45
+#define UUID_BATTERY      0x41
46
+#define UUID_HEATER_ON    0x81
47
+#define UUID_HEATER_OFF   0x91
48
+
49
+int16_t crafty_get_current_temp(void) {
50
+    uuid_base[3] = UUID_CURRENT_TEMP;
51
+
52
+    uint8_t buff[2];
53
+    int32_t r = ble_read(uuid_base, buff, sizeof(buff));
54
+    if (r != sizeof(buff)) {
55
+        debug("ble_read unexpected value %ld", r);
56
+        return -1;
57
+    }
58
+
59
+    uint16_t *v = (uint16_t *)buff;
60
+    return *v;
61
+}
62
+
63
+int16_t crafty_get_target_temp(void) {
64
+    uuid_base[3] = UUID_TARGET_TEMP;
65
+
66
+    uint8_t buff[2];
67
+    int32_t r = ble_read(uuid_base, buff, sizeof(buff));
68
+    if (r != sizeof(buff)) {
69
+        debug("ble_read unexpected value %ld", r);
70
+        return -1;
71
+    }
72
+
73
+    uint16_t *v = (uint16_t *)buff;
74
+    return *v;
75
+}
76
+
77
+int8_t crafty_set_target_temp(uint16_t value) {
78
+    uuid_base[3] = UUID_WRITE_SRVC;
79
+    uuid_base2[3] = UUID_TARGET_TEMP;
80
+
81
+    uint8_t buff[2];
82
+    uint16_t *v = (uint16_t *)buff;
83
+    *v = value;
84
+
85
+    int8_t r = ble_write(uuid_base, uuid_base2, buff, sizeof(buff));
86
+    if (r != 0) {
87
+        debug("ble_write unexpected value %d", r);
88
+    }
89
+    return r;
90
+}
91
+
92
+int8_t crafty_set_heater_state(bool value) {
93
+    uuid_base[3] = UUID_WRITE_SRVC;
94
+
95
+    if (value) {
96
+        uuid_base2[3] = UUID_HEATER_ON;
97
+    } else {
98
+        uuid_base2[3] = UUID_HEATER_OFF;
99
+    }
100
+
101
+    uint16_t d = 0;
102
+    int8_t r = ble_write(uuid_base, uuid_base2, (uint8_t *)&d, sizeof(d));
103
+    if (r != 0) {
104
+        debug("ble_write unexpected value %d", r);
105
+    }
106
+    return r;
107
+}
108
+
109
+int8_t crafty_get_battery_state(void) {
110
+    uuid_base[3] = UUID_BATTERY;
111
+
112
+    uint8_t buff[2];
113
+    int32_t r = ble_read(uuid_base, buff, sizeof(buff));
114
+    if (r != sizeof(buff)) {
115
+        debug("ble_read unexpected value %ld", r);
116
+        return -1;
117
+    }
118
+
119
+    uint16_t *v = (uint16_t *)buff;
120
+    return *v;
121
+}

+ 147
- 0
src/debug_disk.c Bestand weergeven

@@ -0,0 +1,147 @@
1
+/*
2
+ * debug_disk.c
3
+ *
4
+ * Copyright (c) 2022 - 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#include <string.h>
20
+#include <stdio.h>
21
+
22
+#include "pico/stdlib.h"
23
+#include "ff.h"
24
+
25
+#include "config.h"
26
+#include "log.h"
27
+#include "debug_disk.h"
28
+
29
+#ifdef DEBUG_DISK_WRITE_SOURCES
30
+#include "pack_data.h"
31
+#endif // DEBUG_DISK_WRITE_SOURCES
32
+
33
+static FATFS fs;
34
+static bool mounted = false;
35
+
36
+static void write_readme(void) {
37
+    FIL file;
38
+    FRESULT res = f_open(&file, "README.md", FA_CREATE_ALWAYS | FA_WRITE);
39
+    if (res != FR_OK) {
40
+        debug("error: f_open returned %d", res);
41
+    } else {
42
+        char readme[1024];
43
+        size_t pos = 0;
44
+        pos += snprintf(readme + pos, 1024 - pos, "# Volcano Remote Control Gadget\r\n");
45
+        pos += snprintf(readme + pos, 1024 - pos, "\r\n");
46
+        pos += snprintf(readme + pos, 1024 - pos, "Project by Thomas Buck <thomas@xythobuz.de>\r\n");
47
+        pos += snprintf(readme + pos, 1024 - pos, "Licensed under GPLv3.\r\n");
48
+        pos += snprintf(readme + pos, 1024 - pos, "See included 'src.tar.xz' for sources.\r\n");
49
+        pos += snprintf(readme + pos, 1024 - pos, "Repo at https://git.xythobuz.de/thomas/sb-py\r\n");
50
+
51
+        size_t len = strlen(readme);
52
+        UINT bw;
53
+        res = f_write(&file, readme, len, &bw);
54
+        if ((res != FR_OK) || (bw != len)) {
55
+            debug("error: f_write returned %d", res);
56
+        }
57
+
58
+        res = f_close(&file);
59
+        if (res != FR_OK) {
60
+            debug("error: f_close returned %d", res);
61
+        }
62
+    }
63
+}
64
+
65
+#ifdef DEBUG_DISK_WRITE_SOURCES
66
+static void write_sources(void) {
67
+    FIL file;
68
+    FRESULT res = f_open(&file, "src.tar.xz", FA_CREATE_ALWAYS | FA_WRITE);
69
+    if (res != FR_OK) {
70
+        debug("error: f_open returned %d", res);
71
+    } else {
72
+        UINT bw;
73
+        UINT len = 0;
74
+        while (1) {
75
+            res = f_write(&file, data_tar_xz + len, data_tar_xz_len - len, &bw);
76
+            len += bw;
77
+            if (res != FR_OK) {
78
+                debug("error: f_write returned %d", res);
79
+                break;
80
+            } else if (bw == 0) {
81
+                debug("error: f_write did not write");
82
+                break;
83
+            } else if (bw == data_tar_xz_len) {
84
+                break;
85
+            }
86
+        }
87
+
88
+        res = f_close(&file);
89
+        if (res != FR_OK) {
90
+            debug("error: f_close returned %d", res);
91
+        }
92
+    }
93
+}
94
+#endif // DEBUG_DISK_WRITE_SOURCES
95
+
96
+void debug_disk_init(void) {
97
+    if (debug_disk_mount() != 0) {
98
+        debug("error mounting disk");
99
+        return;
100
+    }
101
+
102
+    // maximum length: 11 bytes
103
+    f_setlabel("DEBUG DISK");
104
+
105
+    write_readme();
106
+
107
+#ifdef DEBUG_DISK_WRITE_SOURCES
108
+    write_sources();
109
+#endif // DEBUG_DISK_WRITE_SOURCES
110
+
111
+    if (debug_disk_unmount() != 0) {
112
+        debug("error unmounting disk");
113
+    }
114
+}
115
+
116
+int debug_disk_mount(void) {
117
+    if (mounted) {
118
+        debug("already mounted");
119
+        return 0;
120
+    }
121
+
122
+    FRESULT res = f_mount(&fs, "", 0);
123
+    if (res != FR_OK) {
124
+        debug("error: f_mount returned %d", res);
125
+        mounted = false;
126
+        return -1;
127
+    }
128
+
129
+    mounted = true;
130
+    return 0;
131
+}
132
+
133
+int debug_disk_unmount(void) {
134
+    if (!mounted) {
135
+        debug("already unmounted");
136
+        return 0;
137
+    }
138
+
139
+    FRESULT res = f_mount(0, "", 0);
140
+    if (res != FR_OK) {
141
+        debug("error: f_mount returned %d", res);
142
+        return -1;
143
+    }
144
+
145
+    mounted = false;
146
+    return 0;
147
+}

+ 130
- 0
src/fat_disk.c Bestand weergeven

@@ -0,0 +1,130 @@
1
+/* 
2
+ * fat_disk.c
3
+ *
4
+ * Copyright (c) 2022 - 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#include <string.h>
20
+#include <stdlib.h>
21
+
22
+#include "pico/stdlib.h"
23
+#include "ff.h"
24
+#include "diskio.h"
25
+
26
+#include "config.h"
27
+#include "log.h"
28
+#include "debug_disk.h"
29
+#include "fat_disk.h"
30
+
31
+static uint8_t disk[DISK_BLOCK_COUNT * DISK_BLOCK_SIZE];
32
+
33
+void fat_disk_init(void) {
34
+    BYTE work[FF_MAX_SS];
35
+    FRESULT res = f_mkfs("", 0, work, sizeof(work));
36
+    if (res != FR_OK) {
37
+        debug("error: f_mkfs returned %d", res);
38
+    }
39
+}
40
+
41
+uint8_t *fat_disk_get_sector(uint32_t sector) {
42
+    return disk + (sector * DISK_BLOCK_SIZE);
43
+}
44
+
45
+/*
46
+ * FatFS ffsystem.c
47
+ */
48
+
49
+void* ff_memalloc(UINT msize) {
50
+    return malloc((size_t)msize);
51
+}
52
+
53
+void ff_memfree(void* mblock) {
54
+    free(mblock);
55
+}
56
+
57
+/*
58
+ * FatFS diskio.c
59
+ */
60
+
61
+DSTATUS disk_status(BYTE pdrv) {
62
+    if (pdrv != 0) {
63
+        debug("invalid drive number %d", pdrv);
64
+        return STA_NODISK;
65
+    }
66
+
67
+    return 0;
68
+}
69
+
70
+DSTATUS disk_initialize(BYTE pdrv) {
71
+    if (pdrv != 0) {
72
+        debug("invalid drive number %d", pdrv);
73
+        return STA_NODISK;
74
+    }
75
+
76
+    return 0;
77
+}
78
+
79
+DRESULT disk_read(BYTE pdrv, BYTE *buff, LBA_t sector, UINT count) {
80
+    if (pdrv != 0) {
81
+        debug("invalid drive number %d", pdrv);
82
+        return RES_PARERR;
83
+    }
84
+
85
+    if ((sector + count) > DISK_BLOCK_COUNT) {
86
+        debug("invalid read ((%lu + %u) > %u)", sector, count, DISK_BLOCK_COUNT);
87
+        return RES_ERROR;
88
+    }
89
+
90
+    memcpy(buff, disk + (sector * DISK_BLOCK_SIZE), count * DISK_BLOCK_SIZE);
91
+    return RES_OK;
92
+}
93
+
94
+DRESULT disk_write(BYTE pdrv, const BYTE *buff, LBA_t sector, UINT count) {
95
+    if (pdrv != 0) {
96
+        debug("invalid drive number %d", pdrv);
97
+        return RES_PARERR;
98
+    }
99
+
100
+    if ((sector + count) > DISK_BLOCK_COUNT) {
101
+        debug("invalid read ((%lu + %u) > %u)", sector, count, DISK_BLOCK_COUNT);
102
+        return RES_ERROR;
103
+    }
104
+
105
+    memcpy(disk + (sector * DISK_BLOCK_SIZE), buff, count * DISK_BLOCK_SIZE);
106
+    return RES_OK;
107
+}
108
+
109
+DRESULT disk_ioctl(BYTE pdrv, BYTE cmd, void *buff) {
110
+    if (pdrv != 0) {
111
+        debug("invalid drive number %d", pdrv);
112
+        return RES_PARERR;
113
+    }
114
+
115
+    switch (cmd) {
116
+        case GET_SECTOR_COUNT:
117
+            *((LBA_t *)buff) = DISK_BLOCK_COUNT;
118
+            break;
119
+
120
+        case GET_SECTOR_SIZE:
121
+            *((WORD *)buff) = DISK_BLOCK_SIZE;
122
+            break;
123
+
124
+        case GET_BLOCK_SIZE:
125
+            *((DWORD *)buff) = 1; // non flash memory media
126
+            break;
127
+    }
128
+
129
+    return RES_OK;
130
+}

+ 152
- 0
src/image.c Bestand weergeven

@@ -0,0 +1,152 @@
1
+/*
2
+ * image.c
3
+ *
4
+ * Copyright (c) 2022 - 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#include <stdio.h>
20
+#include <string.h>
21
+
22
+#include "config.h"
23
+#include "lcd.h"
24
+#include "text.h"
25
+#include "lipo.h"
26
+#include "util.h"
27
+#include "image.h"
28
+
29
+#pragma GCC diagnostic push
30
+#pragma GCC diagnostic ignored "-Wtrigraphs"
31
+#include "logo.h"
32
+#pragma GCC diagnostic pop
33
+
34
+#define BATT_INTERVAL_MS 777
35
+
36
+void image_draw(const char *data, uint width, uint height) {
37
+    for (uint x = 0; x < width; x++) {
38
+        for (uint y = 0; y < height; y++) {
39
+            uint32_t pixel[3];
40
+            HEADER_PIXEL(data, pixel);
41
+
42
+            uint32_t color = RGB_565(pixel[0], pixel[1], pixel[2]);
43
+            lcd_write_point(240 - x - 1, y, color);
44
+        }
45
+    }
46
+}
47
+
48
+void draw_splash(void) {
49
+    image_draw(logo_rgb_data, logo_width, logo_height);
50
+
51
+    struct text_font font_big = {
52
+        .fontname = "DejaVuSerif32",
53
+    };
54
+    text_prepare_font(&font_big);
55
+
56
+    struct text_font font_small = {
57
+        .fontname = "DejaVuSerif16",
58
+    };
59
+    text_prepare_font(&font_small);
60
+
61
+    struct text_conf text1 = {
62
+        .text = "xythobuz.de",
63
+        .x = 0,
64
+        .y = 0,
65
+        .justify = false,
66
+        .alignment = MF_ALIGN_CENTER,
67
+        .width = 240,
68
+        .height = 240,
69
+        .margin = 2,
70
+        .fg = RGB_565(0xFF, 0xFF, 0xFF),
71
+        .bg = RGB_565(0x00, 0x00, 0x00),
72
+        .font = &font_big,
73
+    };
74
+    text_draw(&text1);
75
+
76
+    struct text_conf text2 = {
77
+        .text = __DATE__ " @ " __TIME__,
78
+        .x = 0,
79
+        .y = 195,
80
+        .justify = false,
81
+        .alignment = MF_ALIGN_CENTER,
82
+        .width = 240,
83
+        .height = 240,
84
+        .margin = 2,
85
+        .fg = RGB_565(0xFF, 0xFF, 0xFF),
86
+        .bg = RGB_565(0x00, 0x00, 0x00),
87
+        .font = &font_small,
88
+    };
89
+    text_draw(&text2);
90
+}
91
+
92
+void draw_battery_indicator(void) {
93
+    static const float batt_warn_limit = 15.0f;
94
+    static char prev_s[30] = {0};
95
+    static uint32_t prev_c = 0;
96
+
97
+    char s[30] = {0};
98
+    float v = lipo_voltage();
99
+    uint32_t c = RGB_565(0xFF, 0x00, 0x00);
100
+    if (lipo_charging()) {
101
+        //                     "Batt:   99.9%   (4.20V)"
102
+        snprintf(s, sizeof(s), "Batt: Charging! (%.2fV)", v);
103
+        c = RGB_565(0xFF, 0xFF, 0x00);
104
+    } else {
105
+        float percentage = lipo_percentage(v);
106
+        snprintf(s, sizeof(s), "Batt:   %02.1f%%   (%.2fV)", percentage, v);
107
+
108
+        if (percentage > batt_warn_limit) {
109
+            float hue = map(percentage, batt_warn_limit, 100, 0.0, 0.333);
110
+            c = from_hsv(hue, 1.0, 1.0);
111
+        }
112
+    }
113
+
114
+    // only re-draw battery indicator when it has changed
115
+    if ((strcmp(s, prev_s) == 0) && (prev_c == c)) {
116
+        return;
117
+    }
118
+    strcpy(prev_s, s);
119
+    prev_c = c;
120
+
121
+    static struct text_font font = {
122
+        .fontname = "fixed_10x20",
123
+        .font = NULL,
124
+    };
125
+    if (font.font == NULL) {
126
+        text_prepare_font(&font);
127
+    }
128
+
129
+    struct text_conf text = {
130
+        .text = s,
131
+        .x = 0,
132
+        .y = 219,
133
+        .justify = false,
134
+        .alignment = MF_ALIGN_CENTER,
135
+        .width = 240,
136
+        .height = 240,
137
+        .margin = 2,
138
+        .fg = c,
139
+        .bg = RGB_565(0x00, 0x00, 0x00),
140
+        .font = &font,
141
+    };
142
+    text_draw(&text);
143
+}
144
+
145
+void battery_run(void) {
146
+    static uint32_t last_run = 0;
147
+    uint32_t now = to_ms_since_boot(get_absolute_time());
148
+    if ((now >= (last_run + BATT_INTERVAL_MS)) || (last_run == 0)) {
149
+        last_run = now;
150
+        draw_battery_indicator();
151
+    }
152
+}

+ 309
- 0
src/lcd.c Bestand weergeven

@@ -0,0 +1,309 @@
1
+/*
2
+ * lcd.c
3
+ *
4
+ * Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#include <math.h>
20
+
21
+#include "pico/stdlib.h"
22
+#include "hardware/spi.h"
23
+#include "hardware/pwm.h"
24
+#include "driver_st7789.h"
25
+
26
+#include "config.h"
27
+#include "log.h"
28
+#include "lcd.h"
29
+
30
+#define LCD_PIN_DC 8
31
+#define LCD_PIN_CS 9
32
+#define LCD_PIN_CLK 10
33
+#define LCD_PIN_DIN 11
34
+#define LCD_PIN_RST 12
35
+#define LCD_PIN_BL 13
36
+
37
+#define ST7789_PICO_COLUMN                             240
38
+#define ST7789_PICO_ROW                                240
39
+
40
+#define ST7789_PICO_ACCESS                            (ST7789_ORDER_PAGE_TOP_TO_BOTTOM | \
41
+                                                       ST7789_ORDER_COLUMN_LEFT_TO_RIGHT | \
42
+                                                       ST7789_ORDER_PAGE_COLUMN_NORMAL | \
43
+                                                       ST7789_ORDER_LINE_TOP_TO_BOTTOM | \
44
+                                                       ST7789_ORDER_COLOR_RGB | \
45
+                                                       ST7789_ORDER_REFRESH_LEFT_TO_RIGHT)
46
+
47
+#define ST7789_PICO_RGB_INTERFACE_COLOR_FORMAT         0
48
+#define ST7789_PICO_CONTROL_INTERFACE_COLOR_FORMAT     ST7789_CONTROL_INTERFACE_COLOR_FORMAT_16_BIT
49
+
50
+#define ST7789_PICO_PORCH_NORMAL_BACK                  0x0C
51
+#define ST7789_PICO_PORCH_NORMAL_FRONT                 0x0C
52
+#define ST7789_PICO_PORCH_ENABLE                       ST7789_BOOL_FALSE
53
+#define ST7789_PICO_PORCH_IDEL_BACK                    0x03
54
+#define ST7789_PICO_PORCH_IDEL_FRONT                   0x03
55
+#define ST7789_PICO_PORCH_PART_BACK                    0x03
56
+#define ST7789_PICO_PORCH_PART_FRONT                   0x03
57
+
58
+#define ST7789_PICO_VGHS                               ST7789_VGHS_13P26_V
59
+#define ST7789_PICO_VGLS_NEGATIVE                      ST7789_VGLS_NEGATIVE_10P43
60
+
61
+#define ST7789_PICO_VCOMS                              0.725f
62
+
63
+#define ST7789_PICO_XMY                                ST7789_BOOL_FALSE
64
+#define ST7789_PICO_XBGR                               ST7789_BOOL_TRUE
65
+#define ST7789_PICO_XINV                               ST7789_BOOL_FALSE
66
+#define ST7789_PICO_XMX                                ST7789_BOOL_TRUE
67
+#define ST7789_PICO_XMH                                ST7789_BOOL_TRUE
68
+#define ST7789_PICO_XMV                                ST7789_BOOL_FALSE
69
+#define ST7789_PICO_XGS                                ST7789_BOOL_FALSE
70
+
71
+#define ST7789_PICO_VDV_VRH_FROM                       ST7789_VDV_VRH_FROM_CMD
72
+#define ST7789_PICO_VRHS                               4.45f
73
+#define ST7789_PICO_VDV                                0.0f
74
+
75
+#define ST7789_PICO_INVERSION_SELECTION                ST7789_INVERSION_SELECTION_DOT
76
+#define ST7789_PICO_FRAME_RATE                         ST7789_FRAME_RATE_60_HZ
77
+
78
+#define ST7789_PICO_AVDD                               ST7789_AVDD_6P8_V
79
+#define ST7789_PICO_AVCL_NEGTIVE                       ST7789_AVCL_NEGTIVE_4P8_V
80
+#define ST7789_PICO_VDS                                ST7789_VDS_2P3_V
81
+
82
+#define ST7789_PICO_POSITIVE_VOLTAGE_GAMMA            {0xD0, 0x04, 0x0D, 0x11, 0x13, 0x2B, 0x3F, \
83
+                                                       0x54, 0x4C, 0x18, 0x0D, 0x0B, 0x1F, 0x23}
84
+#define ST7789_PICO_NEGATIVA_VOLTAGE_GAMMA            {0xD0, 0x04, 0x0C, 0x11, 0x13, 0x2C, 0x3F, \
85
+                                                       0x44, 0x51, 0x2F, 0x1F, 0x1F, 0x20, 0x23}
86
+
87
+static st7789_handle_t gs_handle;
88
+static uint16_t bl_value = 0;
89
+
90
+static uint8_t st7789_interface_spi_init(void) {
91
+    // Use SPI1 at 100MHz
92
+    spi_init(spi1, 100 * 1000 * 1000);
93
+    gpio_set_function(LCD_PIN_CLK, GPIO_FUNC_SPI);
94
+    gpio_set_function(LCD_PIN_DIN, GPIO_FUNC_SPI);
95
+
96
+    // Chip select is active-low, so we'll initialise it to a driven-high state
97
+    gpio_init(LCD_PIN_CS);
98
+    gpio_set_dir(LCD_PIN_CS, GPIO_OUT);
99
+    gpio_put(LCD_PIN_CS, 1);
100
+
101
+    spi_set_format(spi1,
102
+                   8, // Number of bits per transfer
103
+                   0, // Polarity (CPOL)
104
+                   0, // Phase (CPHA)
105
+                   SPI_MSB_FIRST);
106
+
107
+    return 0;
108
+}
109
+
110
+static uint8_t st7789_interface_spi_deinit(void) {
111
+    spi_deinit(spi1);
112
+    gpio_deinit(LCD_PIN_CS);
113
+    return 0;
114
+}
115
+
116
+static uint8_t st7789_interface_spi_write_cmd(uint8_t *buf, uint16_t len) {
117
+    gpio_put(LCD_PIN_CS, 0);
118
+    spi_write_blocking(spi1, buf, len);
119
+    gpio_put(LCD_PIN_CS, 1);
120
+    return 0;
121
+}
122
+
123
+static void st7789_interface_delay_ms(uint32_t ms) {
124
+    sleep_ms(ms);
125
+}
126
+
127
+static void st7789_interface_debug_print(const char *const fmt, ...) {
128
+    va_list args;
129
+    va_start(args, fmt);
130
+    debug_log_va(true, fmt, args);
131
+    va_end(args);
132
+}
133
+
134
+static uint8_t st7789_interface_cmd_data_gpio_init(void) {
135
+    gpio_init(LCD_PIN_DC);
136
+    gpio_set_dir(LCD_PIN_DC, GPIO_OUT);
137
+    return 0;
138
+}
139
+
140
+static uint8_t st7789_interface_cmd_data_gpio_deinit(void) {
141
+    gpio_deinit(LCD_PIN_DC);
142
+    return 0;
143
+}
144
+
145
+static uint8_t st7789_interface_cmd_data_gpio_write(uint8_t value) {
146
+    gpio_put(LCD_PIN_DC, value);
147
+    return 0;
148
+}
149
+
150
+static uint8_t st7789_interface_reset_gpio_init(void) {
151
+    gpio_init(LCD_PIN_RST);
152
+    gpio_set_dir(LCD_PIN_RST, GPIO_OUT);
153
+    return 0;
154
+}
155
+
156
+static uint8_t st7789_interface_reset_gpio_deinit(void) {
157
+    gpio_deinit(LCD_PIN_RST);
158
+    return 0;
159
+}
160
+
161
+static uint8_t st7789_interface_reset_gpio_write(uint8_t value) {
162
+    gpio_put(LCD_PIN_RST, value);
163
+    return 0;
164
+}
165
+
166
+void lcd_init(void) {
167
+    uint8_t reg;
168
+
169
+    DRIVER_ST7789_LINK_INIT(&gs_handle, st7789_handle_t);
170
+    DRIVER_ST7789_LINK_SPI_INIT(&gs_handle, st7789_interface_spi_init);
171
+    DRIVER_ST7789_LINK_SPI_DEINIT(&gs_handle, st7789_interface_spi_deinit);
172
+    DRIVER_ST7789_LINK_SPI_WRITE_COMMAND(&gs_handle, st7789_interface_spi_write_cmd);
173
+    DRIVER_ST7789_LINK_COMMAND_DATA_GPIO_INIT(&gs_handle, st7789_interface_cmd_data_gpio_init);
174
+    DRIVER_ST7789_LINK_COMMAND_DATA_GPIO_DEINIT(&gs_handle, st7789_interface_cmd_data_gpio_deinit);
175
+    DRIVER_ST7789_LINK_COMMAND_DATA_GPIO_WRITE(&gs_handle, st7789_interface_cmd_data_gpio_write);
176
+    DRIVER_ST7789_LINK_RESET_GPIO_INIT(&gs_handle, st7789_interface_reset_gpio_init);
177
+    DRIVER_ST7789_LINK_RESET_GPIO_DEINIT(&gs_handle, st7789_interface_reset_gpio_deinit);
178
+    DRIVER_ST7789_LINK_RESET_GPIO_WRITE(&gs_handle, st7789_interface_reset_gpio_write);
179
+    DRIVER_ST7789_LINK_DELAY_MS(&gs_handle, st7789_interface_delay_ms);
180
+    DRIVER_ST7789_LINK_DEBUG_PRINT(&gs_handle, st7789_interface_debug_print);
181
+
182
+    st7789_init(&gs_handle);
183
+
184
+    st7789_set_column(&gs_handle, ST7789_PICO_COLUMN);
185
+
186
+    st7789_set_row(&gs_handle, ST7789_PICO_ROW);
187
+
188
+    st7789_set_memory_data_access_control(&gs_handle, ST7789_PICO_ACCESS);
189
+
190
+    st7789_set_interface_pixel_format(&gs_handle,
191
+                                      ST7789_PICO_RGB_INTERFACE_COLOR_FORMAT,
192
+                                      ST7789_PICO_CONTROL_INTERFACE_COLOR_FORMAT);
193
+
194
+    st7789_set_porch(&gs_handle,
195
+                     ST7789_PICO_PORCH_NORMAL_BACK,
196
+                     ST7789_PICO_PORCH_NORMAL_FRONT,
197
+                     ST7789_PICO_PORCH_ENABLE,
198
+                     ST7789_PICO_PORCH_IDEL_BACK,
199
+                     ST7789_PICO_PORCH_IDEL_FRONT,
200
+                     ST7789_PICO_PORCH_PART_BACK,
201
+                     ST7789_PICO_PORCH_PART_FRONT);
202
+
203
+    st7789_set_gate_control(&gs_handle, ST7789_PICO_VGHS, ST7789_PICO_VGLS_NEGATIVE);
204
+
205
+    st7789_vcom_convert_to_register(&gs_handle, ST7789_PICO_VCOMS, &reg);
206
+    st7789_set_vcoms(&gs_handle, reg);
207
+
208
+    st7789_set_lcm_control(&gs_handle,
209
+                           ST7789_PICO_XMY,
210
+                           ST7789_PICO_XBGR,
211
+                           ST7789_PICO_XINV,
212
+                           ST7789_PICO_XMX,
213
+                           ST7789_PICO_XMH,
214
+                           ST7789_PICO_XMV,
215
+                           ST7789_PICO_XGS);
216
+
217
+    st7789_set_vdv_vrh_from(&gs_handle, ST7789_PICO_VDV_VRH_FROM);
218
+
219
+    st7789_vrhs_convert_to_register(&gs_handle, ST7789_PICO_VRHS, &reg);
220
+    st7789_set_vrhs(&gs_handle, reg);
221
+
222
+    st7789_vdv_convert_to_register(&gs_handle, ST7789_PICO_VDV, &reg);
223
+    st7789_set_vdv(&gs_handle, reg);
224
+
225
+    st7789_set_frame_rate(&gs_handle, ST7789_PICO_INVERSION_SELECTION, ST7789_PICO_FRAME_RATE);
226
+
227
+    st7789_set_power_control_1(&gs_handle,
228
+                               ST7789_PICO_AVDD,
229
+                               ST7789_PICO_AVCL_NEGTIVE,
230
+                               ST7789_PICO_VDS);
231
+
232
+    uint8_t param_positive[14] = ST7789_PICO_POSITIVE_VOLTAGE_GAMMA;
233
+    st7789_set_positive_voltage_gamma_control(&gs_handle, param_positive);
234
+
235
+    uint8_t param_negative[14] = ST7789_PICO_NEGATIVA_VOLTAGE_GAMMA;
236
+    st7789_set_negative_voltage_gamma_control(&gs_handle, param_negative);
237
+
238
+    st7789_display_inversion_on(&gs_handle);
239
+
240
+    st7789_sleep_out(&gs_handle);
241
+
242
+    st7789_display_on(&gs_handle);
243
+
244
+    st7789_clear(&gs_handle);
245
+
246
+    // backlight
247
+    uint bl_slice = pwm_gpio_to_slice_num(LCD_PIN_BL);
248
+    uint bl_channel = pwm_gpio_to_channel(LCD_PIN_BL);
249
+    gpio_set_function(LCD_PIN_BL, GPIO_FUNC_PWM);
250
+    pwm_set_wrap(bl_slice, 0xFFFF);
251
+    pwm_set_clkdiv(bl_slice, 1.0f);
252
+    pwm_set_chan_level(bl_slice, bl_channel, bl_value);
253
+    pwm_set_enabled(bl_slice, true);
254
+}
255
+
256
+uint16_t lcd_get_backlight(void) {
257
+    return bl_value;
258
+}
259
+
260
+void lcd_set_backlight(uint16_t value) {
261
+    uint bl_slice = pwm_gpio_to_slice_num(LCD_PIN_BL);
262
+    uint bl_channel = pwm_gpio_to_channel(LCD_PIN_BL);
263
+    bl_value = value;
264
+    pwm_set_chan_level(bl_slice, bl_channel, bl_value);
265
+}
266
+
267
+void lcd_clear(void) {
268
+    st7789_clear(&gs_handle);
269
+}
270
+
271
+void lcd_write_point(uint16_t x, uint16_t y, uint32_t color) {
272
+    st7789_draw_point(&gs_handle, x, y, color);
273
+}
274
+
275
+void lcd_write_rect(uint16_t left, uint16_t top, uint16_t right, uint16_t bottom, uint32_t color) {
276
+    st7789_fill_rect(&gs_handle, left, top, right, bottom, color);
277
+}
278
+
279
+uint32_t from_hsv(float h, float s, float v) {
280
+    float i = floorf(h * 6.0f);
281
+    float f = h * 6.0f - i;
282
+    v *= 255.0f;
283
+    float p = v * (1.0f - s);
284
+    float q = v * (1.0f - f * s);
285
+    float t = v * (1.0f - (1.0f - f) * s);
286
+
287
+    switch (((uint32_t)i) % 6) {
288
+    case 0:
289
+        return RGB_565((uint32_t)v, (uint32_t)t, (uint32_t)p);
290
+
291
+    case 1:
292
+        return RGB_565((uint32_t)q, (uint32_t)v, (uint32_t)p);
293
+
294
+    case 2:
295
+        return RGB_565((uint32_t)p, (uint32_t)v, (uint32_t)t);
296
+
297
+    case 3:
298
+        return RGB_565((uint32_t)p, (uint32_t)q, (uint32_t)v);
299
+
300
+    case 4:
301
+        return RGB_565((uint32_t)t, (uint32_t)p, (uint32_t)v);
302
+
303
+    case 5:
304
+        return RGB_565((uint32_t)v, (uint32_t)p, (uint32_t)q);
305
+
306
+    default:
307
+        return RGB_565(0, 0, 0);
308
+    }
309
+}

+ 120
- 0
src/lipo.c Bestand weergeven

@@ -0,0 +1,120 @@
1
+/*
2
+ * lipo.c
3
+ *
4
+ * https://github.com/raspberrypi/pico-examples/blob/master/adc/read_vsys/power_status.c
5
+ *
6
+ * Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
7
+ *
8
+ * This program is free software: you can redistribute it and/or modify
9
+ * it under the terms of the GNU General Public License as published by
10
+ * the Free Software Foundation, either version 3 of the License, or
11
+ * (at your option) any later version.
12
+ *
13
+ * This program is distributed in the hope that it will be useful,
14
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
+ * GNU General Public License for more details.
17
+ *
18
+ * See <http://www.gnu.org/licenses/>.
19
+ */
20
+
21
+#include <math.h>
22
+
23
+#include "stdbool.h"
24
+#include "hardware/adc.h"
25
+
26
+#include "config.h"
27
+#include "lipo.h"
28
+
29
+#if CYW43_USES_VSYS_PIN
30
+#include "pico/cyw43_arch.h"
31
+#endif
32
+
33
+#ifndef PICO_POWER_SAMPLE_COUNT
34
+#define PICO_POWER_SAMPLE_COUNT 3
35
+#endif
36
+
37
+// Pin used for ADC 0
38
+#define PICO_FIRST_ADC_PIN 26
39
+
40
+static const float full_battery = 4.1f;
41
+static const float empty_battery = 3.2f;
42
+static const float low_pass_factor = 0.9f;
43
+
44
+bool lipo_charging(void) {
45
+#if defined CYW43_WL_GPIO_VBUS_PIN
46
+    return cyw43_arch_gpio_get(CYW43_WL_GPIO_VBUS_PIN);
47
+#elif defined PICO_VBUS_PIN
48
+    gpio_set_function(PICO_VBUS_PIN, GPIO_FUNC_SIO);
49
+    return gpio_get(PICO_VBUS_PIN);
50
+#else
51
+#error "No VBUS Pin available!"
52
+#endif
53
+}
54
+
55
+float lipo_voltage(void) {
56
+#ifndef PICO_VSYS_PIN
57
+#error "No VSYS Pin available!"
58
+#endif
59
+
60
+#if CYW43_USES_VSYS_PIN
61
+    cyw43_thread_enter();
62
+    // Make sure cyw43 is awake
63
+    bool charging = cyw43_arch_gpio_get(CYW43_WL_GPIO_VBUS_PIN);
64
+#endif
65
+
66
+    // setup adc
67
+    adc_gpio_init(PICO_VSYS_PIN);
68
+    adc_select_input(PICO_VSYS_PIN - PICO_FIRST_ADC_PIN);
69
+
70
+    adc_fifo_setup(true, false, 0, false, false);
71
+    adc_run(true);
72
+
73
+    // We seem to read low values initially - this seems to fix it
74
+    int ignore_count = PICO_POWER_SAMPLE_COUNT;
75
+    while (!adc_fifo_is_empty() || ignore_count-- > 0) {
76
+        (void)adc_fifo_get_blocking();
77
+    }
78
+
79
+    // read vsys
80
+    uint32_t vsys = 0;
81
+    for(int i = 0; i < PICO_POWER_SAMPLE_COUNT; i++) {
82
+        uint16_t val = adc_fifo_get_blocking();
83
+        vsys += val;
84
+    }
85
+
86
+    adc_run(false);
87
+    adc_fifo_drain();
88
+
89
+    vsys /= PICO_POWER_SAMPLE_COUNT;
90
+
91
+#if CYW43_USES_VSYS_PIN
92
+    cyw43_thread_exit();
93
+#endif
94
+
95
+    // Generate voltage
96
+    const float conversion_factor = 3.3f / (1 << 12);
97
+    float v_now = vsys * 3 * conversion_factor;
98
+
99
+    static float v_prev = NAN;
100
+    if (charging) {
101
+        v_prev = NAN;
102
+        return v_now;
103
+    } else {
104
+        if (isnan(v_prev)) {
105
+            v_prev = v_now;
106
+        }
107
+        v_prev = (v_prev * low_pass_factor) + (v_now * (1.0f - low_pass_factor));
108
+        return v_prev;
109
+    }
110
+}
111
+
112
+float lipo_percentage(float voltage) {
113
+    float percentage = 100.0f * ((voltage - empty_battery) / (full_battery - empty_battery));
114
+    if (percentage >= 99.9f) {
115
+        percentage = 99.9f;
116
+    } else if (percentage < 0.0f) {
117
+        percentage = 0.0f;
118
+    }
119
+    return percentage;
120
+}

+ 142
- 0
src/log.c Bestand weergeven

@@ -0,0 +1,142 @@
1
+/*
2
+ * log.c
3
+ *
4
+ * Copyright (c) 2022 - 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#include <stdio.h>
20
+
21
+#include "hardware/watchdog.h"
22
+#include "ff.h"
23
+
24
+#include "config.h"
25
+#include "main.h"
26
+#include "usb_cdc.h"
27
+#include "serial.h"
28
+#include "ring.h"
29
+#include "log.h"
30
+
31
+static uint8_t log_buff[4096] = {0};
32
+static struct ring_buffer log = RB_INIT(log_buff, sizeof(log_buff));
33
+
34
+static uint8_t line_buff[128] = {0};
35
+static volatile bool got_input = false;
36
+static FIL log_file_fat;
37
+
38
+static void add_to_log(const uint8_t *buff, size_t len) {
39
+    rb_add(&log, buff, len);
40
+}
41
+
42
+static void log_dump_to_x(void (*write)(const uint8_t *, size_t)) {
43
+    if (rb_len(&log) == 0) {
44
+        return;
45
+    }
46
+
47
+    int l = snprintf((char *)line_buff, sizeof(line_buff), "\r\n\r\nbuffered log output:\r\n");
48
+    if ((l > 0) && (l <= (int)sizeof(line_buff))) {
49
+        write(line_buff, l);
50
+    }
51
+
52
+    rb_dump(&log, write);
53
+
54
+    l = snprintf((char *)line_buff, sizeof(line_buff), "\r\n\r\nlive log:\r\n");
55
+    if ((l > 0) && (l <= (int)sizeof(line_buff))) {
56
+        write(line_buff, l);
57
+    }
58
+}
59
+
60
+void log_dump_to_usb(void) {
61
+    log_dump_to_x(usb_cdc_write);
62
+}
63
+
64
+void log_dump_to_uart(void) {
65
+    log_dump_to_x(serial_write);
66
+}
67
+
68
+static void log_file_write_callback(const uint8_t *data, size_t len) {
69
+    UINT bw;
70
+    FRESULT res = f_write(&log_file_fat, data, len, &bw);
71
+    if ((res != FR_OK) || (bw != len)) {
72
+        debug("error: f_write %u returned %d", len, res);
73
+    }
74
+}
75
+
76
+void log_dump_to_disk(void) {
77
+    FRESULT res = f_open(&log_file_fat, "log.txt", FA_CREATE_ALWAYS | FA_WRITE);
78
+    if (res != FR_OK) {
79
+        debug("error: f_open returned %d", res);
80
+        return;
81
+    }
82
+
83
+    rb_dump(&log, log_file_write_callback);
84
+
85
+    res = f_close(&log_file_fat);
86
+    if (res != FR_OK) {
87
+        debug("error: f_close returned %d", res);
88
+    }
89
+}
90
+
91
+void debug_log_va(bool log, const char *format, va_list args) {
92
+    int l = vsnprintf((char *)line_buff, sizeof(line_buff), format, args);
93
+
94
+    if (l < 0) {
95
+        // encoding error
96
+        l = snprintf((char *)line_buff, sizeof(line_buff), "%s: encoding error\r\n", __func__);
97
+    } else if (l >= (ssize_t)sizeof(line_buff)) {
98
+        // not enough space for string
99
+        l = snprintf((char *)line_buff, sizeof(line_buff), "%s: message too long (%d)\r\n", __func__, l);
100
+    }
101
+    if ((l > 0) && (l <= (int)sizeof(line_buff))) {
102
+        usb_cdc_write(line_buff, l);
103
+        serial_write(line_buff, l);
104
+
105
+        if (log) {
106
+            add_to_log(line_buff, l);
107
+        }
108
+    }
109
+}
110
+
111
+void debug_log(bool log, const char* format, ...) {
112
+    va_list args;
113
+    va_start(args, format);
114
+    debug_log_va(log, format, args);
115
+    va_end(args);
116
+}
117
+
118
+void debug_handle_input(const uint8_t *buff, size_t len) {
119
+    (void)buff;
120
+
121
+    if (len > 0) {
122
+        got_input = true;
123
+    }
124
+}
125
+
126
+void debug_wait_input(const char *format, ...) {
127
+    va_list args;
128
+    va_start(args, format);
129
+    debug_log_va(false, format, args);
130
+    va_end(args);
131
+
132
+    got_input = false;
133
+    usb_cdc_set_reroute(true);
134
+    serial_set_reroute(true);
135
+
136
+    while (!got_input) {
137
+        main_loop_hw();
138
+    }
139
+
140
+    usb_cdc_set_reroute(false);
141
+    serial_set_reroute(false);
142
+}

+ 112
- 0
src/main.c Bestand weergeven

@@ -0,0 +1,112 @@
1
+/*
2
+ * main.c
3
+ *
4
+ * Copyright (c) 2022 - 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#include "pico/stdlib.h"
20
+#include "pico/cyw43_arch.h"
21
+#include "hardware/watchdog.h"
22
+#include "hardware/adc.h"
23
+
24
+#include "config.h"
25
+#include "util.h"
26
+#include "console.h"
27
+#include "log.h"
28
+#include "usb.h"
29
+#include "fat_disk.h"
30
+#include "debug_disk.h"
31
+#include "buttons.h"
32
+#include "ble.h"
33
+#include "lcd.h"
34
+#include "text.h"
35
+#include "image.h"
36
+#include "state.h"
37
+#include "serial.h"
38
+#include "workflow.h"
39
+
40
+void main_loop_hw(void) {
41
+    watchdog_update();
42
+    usb_run();
43
+    serial_run();
44
+    heartbeat_run();
45
+}
46
+
47
+int main(void) {
48
+    // required for debug console
49
+    cnsl_init();
50
+    serial_init();
51
+    usb_init();
52
+
53
+    debug("lcd_init");
54
+    lcd_init();
55
+    draw_splash();
56
+    lcd_set_backlight(0x8000);
57
+
58
+    if (watchdog_caused_reboot()) {
59
+        debug("reset by watchdog");
60
+    }
61
+
62
+    debug("buttons_init");
63
+    buttons_init();
64
+
65
+    // required for LiPo voltage reading
66
+    debug("adc_init");
67
+    adc_init();
68
+
69
+    // required for BLE and LiPo voltage reading
70
+    debug("cyw43_arch_init");
71
+    if (cyw43_arch_init()) {
72
+        debug("cyw43_arch_init failed");
73
+    }
74
+
75
+    debug("heartbeat_init");
76
+    heartbeat_init();
77
+
78
+    debug("ble_init");
79
+    ble_init();
80
+
81
+    debug("fat_disk_init");
82
+    fat_disk_init();
83
+
84
+    debug("debug_disk_init");
85
+    debug_disk_init();
86
+
87
+    watchdog_enable(WATCHDOG_PERIOD_MS, 1);
88
+
89
+    debug("init done");
90
+    battery_run();
91
+    lcd_set_backlight(0x8000); // TODO dynamic
92
+
93
+    // wait for BLE stack to be ready before using it
94
+    debug("wait for bt stack");
95
+    while (!ble_is_ready()) {
96
+        sleep_ms(1);
97
+    }
98
+
99
+    debug("starting app");
100
+    state_switch(STATE_SCAN);
101
+
102
+    while (1) {
103
+        main_loop_hw();
104
+        buttons_run();
105
+        cnsl_run();
106
+        battery_run();
107
+        state_run();
108
+        wf_run();
109
+    }
110
+
111
+    return 0;
112
+}

+ 105
- 0
src/menu.c Bestand weergeven

@@ -0,0 +1,105 @@
1
+/*
2
+ * menu.c
3
+ *
4
+ * Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#include <string.h>
20
+
21
+#include "config.h"
22
+#include "buttons.h"
23
+#include "text.h"
24
+#include "menu.h"
25
+
26
+static char prev_buff[MENU_MAX_LEN] = {0};
27
+static char buff[MENU_MAX_LEN] = {0};
28
+static struct menu_state menu = { .off = 0, .selection = -1, .length = 0, .buff = buff };
29
+static void (*enter_callback)(int) = NULL;
30
+static void (*exit_callback)(void) = NULL;
31
+
32
+static void menu_buttons(enum buttons btn, bool state) {
33
+    if (state && (btn == BTN_LEFT)) {
34
+        // TODO brightness down
35
+        return;
36
+    } else if (state && (btn == BTN_RIGHT)) {
37
+        // TODO brightness up
38
+        return;
39
+    } else if (state && ((btn == BTN_ENTER) || (btn == BTN_A))) {
40
+        if (enter_callback) {
41
+            enter_callback(menu.selection);
42
+        }
43
+        return;
44
+    } else if (state && (btn == BTN_Y)) {
45
+        if (exit_callback) {
46
+            exit_callback();
47
+        }
48
+    } else if ((!state) || ((btn != BTN_UP) && (btn != BTN_DOWN))) {
49
+        return;
50
+    }
51
+
52
+    if (state && (btn == BTN_UP)) {
53
+        if (menu.selection < 0) {
54
+            menu.selection = menu.length - 1;
55
+        } else {
56
+            menu.selection -= 1;
57
+        }
58
+    } else if (state && (btn == BTN_DOWN)) {
59
+        if (menu.selection < 0) {
60
+            menu.selection = 0;
61
+        } else {
62
+            menu.selection += 1;
63
+        }
64
+    }
65
+
66
+    if (menu.selection < 0) {
67
+        menu.selection += menu.length;
68
+    }
69
+    if (menu.selection >= menu.length) {
70
+        menu.selection -= menu.length;
71
+    }
72
+    if (menu.selection >= 0) {
73
+        while (menu.selection < menu.off) {
74
+            menu.off -= 1;
75
+        }
76
+        while (menu.selection >= (menu.off + MENU_MAX_LINES)) {
77
+            menu.off += 1;
78
+        }
79
+    }
80
+}
81
+
82
+void menu_init(void (*enter)(int), void (*exit)(void)) {
83
+    menu.off = 0;
84
+    menu.selection = -1;
85
+    menu.length = 0;
86
+
87
+    enter_callback = enter;
88
+    exit_callback = exit;
89
+    buttons_callback(menu_buttons);
90
+}
91
+
92
+void menu_deinit(void) {
93
+    buttons_callback(NULL);
94
+}
95
+
96
+void menu_run(void (*draw)(struct menu_state *)) {
97
+    if (draw) {
98
+        draw(&menu);
99
+    }
100
+
101
+    if (strncmp(buff, prev_buff, MENU_MAX_LEN) != 0) {
102
+        strncpy(prev_buff, buff, MENU_MAX_LEN);
103
+        text_box(buff);
104
+    }
105
+}

+ 53
- 0
src/models.c Bestand weergeven

@@ -0,0 +1,53 @@
1
+/*
2
+ * models.c
3
+ *
4
+ * Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#include <stddef.h>
20
+#include <string.h>
21
+
22
+#include "config.h"
23
+#include "log.h"
24
+#include "util.h"
25
+#include "models.h"
26
+
27
+enum known_devices models_filter_name(const char *name) {
28
+    if (name == NULL) {
29
+        return DEV_UNKNOWN;
30
+    } else if (strcmp(name, "S&B VOLCANO H") == 0) {
31
+        return DEV_VOLCANO;
32
+    } else if (strcmp(name, "STORZ&BICKEL") == 0) {
33
+        return DEV_CRAFTY;
34
+    } else {
35
+        return DEV_UNKNOWN;
36
+    }
37
+}
38
+
39
+int8_t models_get_serial(const uint8_t *data, size_t data_len, char *buff, size_t buff_len) {
40
+    if ((data == NULL) || (data_len <= 0) || (buff == NULL) || (buff_len <= 0)) {
41
+        return -1;
42
+    }
43
+
44
+    const size_t serial_len = 8;
45
+    const size_t serial_off = 2;
46
+    if ((data_len < (serial_len + serial_off)) || (buff_len < (serial_len + 1))) {
47
+        return -2;
48
+    }
49
+    memcpy(buff, data + serial_off, serial_len);
50
+    buff[serial_len] = '\0';
51
+
52
+    return 0;
53
+}

+ 92
- 0
src/ring.c Bestand weergeven

@@ -0,0 +1,92 @@
1
+/*
2
+ * ring.c
3
+ *
4
+ * Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#include "config.h"
20
+#include "ring.h"
21
+
22
+void rb_add(struct ring_buffer *rb, const uint8_t *data, size_t length) {
23
+    for (size_t i = 0; i < length; i++) {
24
+        rb->buffer[rb->head] = data[i];
25
+
26
+        if (rb->full && (++(rb->tail) == rb->size)) {
27
+            rb->tail = 0;
28
+        }
29
+
30
+        if (++(rb->head) == rb->size) {
31
+            rb->head = 0;
32
+        }
33
+
34
+        rb->full = ((rb->head) == (rb->tail));
35
+    }
36
+}
37
+
38
+size_t rb_len(struct ring_buffer *rb) {
39
+    if (rb->head == rb->tail) {
40
+        if (rb->full) {
41
+            return rb->size;
42
+        } else {
43
+            return 0;
44
+        }
45
+    } else if (rb->head > rb->tail) {
46
+        return rb->head - rb->tail;
47
+    } else {
48
+        return rb->size - rb->tail + rb->head;
49
+    }
50
+}
51
+
52
+void rb_dump(struct ring_buffer *rb, void (*write)(const uint8_t *, size_t)) {
53
+    if (rb_len(rb) == 0) {
54
+        return;
55
+    }
56
+
57
+    if (rb->head > rb->tail) {
58
+        write(rb->buffer + rb->tail, rb->head - rb->tail);
59
+    } else {
60
+        write(rb->buffer + rb->tail, rb->size - rb->tail);
61
+        write(rb->buffer, rb->head);
62
+    }
63
+}
64
+
65
+void rb_move(struct ring_buffer *rb, void (*write)(const uint8_t *, size_t)) {
66
+    rb_dump(rb, write);
67
+    rb->head = 0;
68
+    rb->tail = 0;
69
+    rb->full = false;
70
+}
71
+
72
+uint8_t rb_peek(struct ring_buffer *rb) {
73
+    if (rb_len(rb) == 0) {
74
+        return 0;
75
+    }
76
+
77
+    uint8_t v = rb->buffer[rb->tail];
78
+    return v;
79
+}
80
+
81
+uint8_t rb_pop(struct ring_buffer *rb) {
82
+    if (rb_len(rb) == 0) {
83
+        return 0;
84
+    }
85
+
86
+    uint8_t v = rb->buffer[rb->tail++];
87
+    if (rb->tail >= rb->size) {
88
+        rb->tail = 0;
89
+    }
90
+
91
+    return v;
92
+}

+ 157
- 0
src/serial.c Bestand weergeven

@@ -0,0 +1,157 @@
1
+/*
2
+ * serial.c
3
+ *
4
+ * https://github.com/raspberrypi/pico-examples/blob/master/uart/uart_advanced/uart_advanced.c
5
+ * https://forums.raspberrypi.com/viewtopic.php?t=343110
6
+ *
7
+ * Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
8
+ *
9
+ * This program is free software: you can redistribute it and/or modify
10
+ * it under the terms of the GNU General Public License as published by
11
+ * the Free Software Foundation, either version 3 of the License, or
12
+ * (at your option) any later version.
13
+ *
14
+ * This program is distributed in the hope that it will be useful,
15
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
+ * GNU General Public License for more details.
18
+ *
19
+ * See <http://www.gnu.org/licenses/>.
20
+ */
21
+
22
+// UART0, Tx GP0, Rx GP1
23
+#define UART_ID uart0
24
+#define UART_IRQ UART0_IRQ
25
+#define UART_TX_PIN 0
26
+#define UART_RX_PIN 1
27
+
28
+// Serial port parameters
29
+#define BAUD_RATE 115200
30
+#define DATA_BITS 8
31
+#define PARITY UART_PARITY_NONE
32
+#define STOP_BITS 1
33
+
34
+#define UART_RX_BUFF_LEN 64
35
+#define UART_TX_BUFF_LEN 128
36
+
37
+#include "hardware/uart.h"
38
+#include "hardware/gpio.h"
39
+
40
+#include "config.h"
41
+#include "console.h"
42
+#include "log.h"
43
+#include "util.h"
44
+#include "ring.h"
45
+#include "serial.h"
46
+
47
+static uint8_t rx_buff[UART_RX_BUFF_LEN] = {0};
48
+static struct ring_buffer rx = RB_INIT(rx_buff, sizeof(rx_buff));
49
+
50
+static uint8_t tx_buff[UART_TX_BUFF_LEN] = {0};
51
+static struct ring_buffer tx = RB_INIT(tx_buff, sizeof(tx_buff));
52
+
53
+static bool reroute_serial_debug = false;
54
+static bool tx_irq_state = false;
55
+static bool rx_irq_state = false;
56
+#define SET_TX_IRQ(v) {                 \
57
+    tx_irq_state = v;                   \
58
+    uart_set_irq_enables(UART_ID,       \
59
+                         rx_irq_state,  \
60
+                         tx_irq_state); \
61
+}
62
+#define SET_RX_IRQ(v) {                 \
63
+    rx_irq_state = v;                   \
64
+    uart_set_irq_enables(UART_ID,       \
65
+                         rx_irq_state,  \
66
+                         tx_irq_state); \
67
+}
68
+
69
+static void serial_irq(void) {
70
+    // Rx - read from UART FIFO to local buffer
71
+    while (uart_is_readable(UART_ID) && (rb_space(&rx) > 0)) {
72
+        uint8_t ch = uart_getc(UART_ID);
73
+        rb_push(&rx, ch);
74
+    }
75
+
76
+    // Tx - write to UART FIFO if needed
77
+    while (uart_is_writable(UART_ID)) {
78
+        if (rb_len(&tx) > 0) {
79
+            uart_putc_raw(UART_ID, rb_pop(&tx));
80
+        } else {
81
+            SET_TX_IRQ(false);
82
+            break;
83
+        }
84
+    }
85
+
86
+}
87
+
88
+void serial_init(void) {
89
+    uart_init(UART_ID, BAUD_RATE);
90
+
91
+    gpio_set_function(UART_TX_PIN, GPIO_FUNC_UART);
92
+    gpio_set_function(UART_RX_PIN, GPIO_FUNC_UART);
93
+
94
+    uart_set_hw_flow(UART_ID, false, false);
95
+    uart_set_format(UART_ID, DATA_BITS, STOP_BITS, PARITY);
96
+    uart_set_fifo_enabled(UART_ID, true);
97
+
98
+    irq_set_exclusive_handler(UART_IRQ, serial_irq);
99
+    irq_set_enabled(UART_IRQ, true);
100
+
101
+    SET_RX_IRQ(true);
102
+    SET_TX_IRQ(false);
103
+}
104
+
105
+void serial_write(const uint8_t *buf, size_t count) {
106
+    SET_TX_IRQ(false);
107
+
108
+    while ((rb_len(&tx) == 0) && uart_is_writable(UART_ID) && (count > 0)) {
109
+        uart_putc_raw(UART_ID, *buf++);
110
+        count--;
111
+    }
112
+
113
+    if (count == 0) {
114
+        return;
115
+    }
116
+
117
+    size_t off = 0;
118
+
119
+#ifdef SERIAL_WRITES_BLOCK_WHEN_BUFFER_FULL
120
+    while (count > rb_space(&tx)) {
121
+        size_t space = rb_space(&tx);
122
+        rb_add(&tx, buf + off, space);
123
+        count -= space;
124
+        off += space;
125
+
126
+        SET_TX_IRQ(true);
127
+
128
+        sleep_ms(1);
129
+
130
+        SET_TX_IRQ(false);
131
+    }
132
+#endif // SERIAL_WRITES_BLOCK_WHEN_BUFFER_FULL
133
+
134
+    rb_add(&tx, buf + off, count);
135
+
136
+    SET_TX_IRQ(true);
137
+}
138
+
139
+void serial_set_reroute(bool reroute) {
140
+    reroute_serial_debug = reroute;
141
+}
142
+
143
+void serial_run(void) {
144
+    SET_RX_IRQ(false);
145
+
146
+    if (rb_len(&rx) >= 1) {
147
+        if (rb_peek(&rx) == ENTER_BOOTLOADER_MAGIC) {
148
+            reset_to_bootloader();
149
+        } else if (reroute_serial_debug) {
150
+            rb_move(&rx, debug_handle_input);
151
+        } else {
152
+            rb_move(&rx, cnsl_handle_input);
153
+        }
154
+    }
155
+
156
+    SET_RX_IRQ(true);
157
+}

+ 115
- 0
src/state.c Bestand weergeven

@@ -0,0 +1,115 @@
1
+/*
2
+ * state.c
3
+ *
4
+ * Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#include "config.h"
20
+#include "log.h"
21
+#include "state_scan.h"
22
+#include "state_volcano_workflow.h"
23
+#include "state_volcano_run.h"
24
+#include "state_crafty.h"
25
+#include "state.h"
26
+
27
+static enum system_state state = STATE_INIT;
28
+
29
+void state_switch(enum system_state next) {
30
+    if (state == next) {
31
+        return;
32
+    }
33
+
34
+    // clean up old state when leaving it
35
+    switch (state) {
36
+    case STATE_SCAN:
37
+        debug("leaving STATE_SCAN");
38
+        state_scan_exit();
39
+        break;
40
+
41
+    case STATE_VOLCANO_WORKFLOW:
42
+        debug("leaving STATE_VOLCANO_WORKFLOW");
43
+        state_volcano_wf_exit();
44
+        break;
45
+
46
+    case STATE_VOLCANO_RUN:
47
+        debug("leaving STATE_VOLCANO_RUN");
48
+        state_volcano_run_exit();
49
+        break;
50
+
51
+    case STATE_CRAFTY:
52
+        debug("leaving STATE_CRAFTY");
53
+        state_crafty_exit();
54
+        break;
55
+
56
+    default:
57
+        break;
58
+    }
59
+
60
+    // prepare new state on entering
61
+    switch (next) {
62
+    case STATE_SCAN:
63
+        debug("entering STATE_SCAN");
64
+        state_scan_enter();
65
+        break;
66
+
67
+    case STATE_VOLCANO_WORKFLOW:
68
+        debug("entering STATE_VOLCANO_WORKFLOW");
69
+        state_volcano_wf_enter();
70
+        break;
71
+
72
+    case STATE_VOLCANO_RUN:
73
+        debug("entering STATE_VOLCANO_RUN");
74
+        state_volcano_run_enter();
75
+        break;
76
+
77
+    case STATE_CRAFTY:
78
+        debug("entering STATE_CRAFTY");
79
+        state_crafty_enter();
80
+        break;
81
+
82
+    default:
83
+        break;
84
+    }
85
+
86
+    state = next;
87
+}
88
+
89
+void state_run(void) {
90
+    switch (state) {
91
+    case STATE_INIT:
92
+        break;
93
+
94
+    case STATE_SCAN:
95
+        state_scan_run();
96
+        break;
97
+
98
+    case STATE_VOLCANO_WORKFLOW:
99
+        state_volcano_wf_run();
100
+        break;
101
+
102
+    case STATE_VOLCANO_RUN:
103
+        state_volcano_run_run();
104
+        break;
105
+
106
+    case STATE_CRAFTY:
107
+        state_crafty_run();
108
+        break;
109
+
110
+    default:
111
+        debug("invalid main state %d", state);
112
+        state_switch(STATE_SCAN);
113
+        break;
114
+    }
115
+}

+ 127
- 0
src/state_crafty.c Bestand weergeven

@@ -0,0 +1,127 @@
1
+/*
2
+ * state_crafty.c
3
+ *
4
+ * Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#include <stdio.h>
20
+#include <string.h>
21
+
22
+#include "config.h"
23
+#include "buttons.h"
24
+#include "crafty.h"
25
+#include "log.h"
26
+#include "state.h"
27
+#include "state_crafty.h"
28
+
29
+#include "menu.h"
30
+
31
+#define CRAFTY_UPDATE_TIME_MS 1000
32
+
33
+static bd_addr_t ble_addr = {0};
34
+static bd_addr_type_t ble_type = 0;
35
+static bool wait_for_connect = false;
36
+static bool wait_for_disconnect = false;
37
+static bool heater_state = false;
38
+static int16_t target_temp = 0;
39
+
40
+static void crafty_buttons(enum buttons btn, bool state) {
41
+    if (state && (btn == BTN_Y)) {
42
+        if ((!wait_for_connect) && (!wait_for_disconnect)) {
43
+            debug("crafty disconnect");
44
+            ble_disconnect();
45
+            wait_for_disconnect = true;
46
+        } else {
47
+            debug("invalid state for disconnect");
48
+        }
49
+    } else if (state && (btn == BTN_A)) {
50
+        heater_state = !heater_state;
51
+        debug("crafty heater %s", heater_state ? "on" : "off");
52
+        crafty_set_heater_state(heater_state);
53
+    } else if (state && (btn == BTN_LEFT)) {
54
+        target_temp -= 10;
55
+        if (target_temp < 400) {
56
+            target_temp = 400;
57
+        } else if (target_temp > 2100) {
58
+            target_temp = 2100;
59
+        }
60
+        debug("crafty temp to %.1f", target_temp / 10.0f);
61
+        crafty_set_target_temp(target_temp);
62
+    } else if (state && (btn == BTN_RIGHT)) {
63
+        target_temp += 10;
64
+        if (target_temp < 400) {
65
+            target_temp = 400;
66
+        } else if (target_temp > 2100) {
67
+            target_temp = 2100;
68
+        }
69
+        debug("crafty temp to %.1f", target_temp / 10.0f);
70
+        crafty_set_target_temp(target_temp);
71
+    }
72
+}
73
+
74
+void state_crafty_target(bd_addr_t addr, bd_addr_type_t type) {
75
+    memcpy(ble_addr, addr, sizeof(bd_addr_t));
76
+    ble_type = type;
77
+}
78
+
79
+void state_crafty_enter(void) {
80
+    debug("crafty connect");
81
+    ble_connect(ble_addr, ble_type);
82
+    wait_for_connect = true;
83
+
84
+    buttons_callback(crafty_buttons);
85
+}
86
+
87
+void state_crafty_exit(void) {
88
+    buttons_callback(NULL);
89
+}
90
+
91
+static void draw(struct menu_state *menu) {
92
+    if (wait_for_connect) {
93
+        snprintf(menu->buff, MENU_MAX_LEN, "Connecting...");
94
+    } else if (wait_for_disconnect) {
95
+        snprintf(menu->buff, MENU_MAX_LEN, "Disconnecting...");
96
+    } else {
97
+        target_temp = crafty_get_target_temp();
98
+        int16_t ct = crafty_get_current_temp();
99
+        int8_t bs = crafty_get_battery_state();
100
+
101
+        snprintf(menu->buff, MENU_MAX_LEN,
102
+                 "Target: %.1f C\n"
103
+                 "Current: %.1f C\n"
104
+                 "Battery: %d %%",
105
+                 target_temp / 10.0f, ct / 10.0f, bs);
106
+    }
107
+}
108
+
109
+void state_crafty_run(void) {
110
+    if (wait_for_connect && ble_is_connected()) {
111
+        wait_for_connect = false;
112
+        debug("crafty start");
113
+    }
114
+
115
+    static uint32_t last = 0;
116
+    uint32_t now = to_ms_since_boot(get_absolute_time());
117
+    if (((now - last) >= CRAFTY_UPDATE_TIME_MS) || (last == 0)) {
118
+        last = now;
119
+        menu_run(draw);
120
+    }
121
+
122
+    if (wait_for_disconnect && !ble_is_connected()) {
123
+        wait_for_disconnect = false;
124
+        debug("crafty done");
125
+        state_switch(STATE_SCAN);
126
+    }
127
+}

+ 132
- 0
src/state_scan.c Bestand weergeven

@@ -0,0 +1,132 @@
1
+/*
2
+ * state_scan.c
3
+ *
4
+ * Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#include <stdio.h>
20
+#include <string.h>
21
+
22
+#include "config.h"
23
+#include "ble.h"
24
+#include "models.h"
25
+#include "menu.h"
26
+#include "state.h"
27
+#include "state_volcano_run.h"
28
+#include "state_crafty.h"
29
+#include "state_scan.h"
30
+
31
+static struct ble_scan_result results[BLE_MAX_SCAN_RESULTS] = {0};
32
+static int result_count = 0;
33
+
34
+static void enter_cb(int selection) {
35
+    int devs = 0;
36
+    for (int i = 0; i < result_count; i++) {
37
+        enum known_devices dev = models_filter_name(results[i].name);
38
+        if (dev == DEV_UNKNOWN) {
39
+            continue;
40
+        }
41
+
42
+        if (devs++ == selection) {
43
+            if (dev == DEV_VOLCANO) {
44
+                state_volcano_run_target(results[i].addr, results[i].type);
45
+                state_switch(STATE_VOLCANO_WORKFLOW);
46
+            } else if (dev == DEV_CRAFTY) {
47
+                state_crafty_target(results[i].addr, results[i].type);
48
+                state_switch(STATE_CRAFTY);
49
+            }
50
+            return;
51
+        }
52
+    }
53
+}
54
+
55
+void state_scan_enter(void) {
56
+    menu_init(enter_cb, NULL);
57
+    ble_scan(BLE_SCAN_ON);
58
+}
59
+
60
+void state_scan_exit(void) {
61
+    menu_deinit();
62
+    ble_scan(BLE_SCAN_OFF);
63
+}
64
+
65
+static void draw(struct menu_state *menu) {
66
+    result_count = ble_get_scan_results(results, BLE_MAX_SCAN_RESULTS);
67
+
68
+    int pos = 0, devs = 0;
69
+    for (int i = 0; i < result_count; i++) {
70
+        enum known_devices dev = models_filter_name(results[i].name);
71
+        if (dev == DEV_UNKNOWN) {
72
+            continue;
73
+        }
74
+
75
+        devs++;
76
+
77
+        if (((devs - 1) < menu->off)
78
+            || ((devs - 1 - menu->off) >= MENU_MAX_LINES)) {
79
+            continue;
80
+        }
81
+
82
+#if defined(MENU_PREFER_VOLCANO) || defined(MENU_PREFER_CRAFTY)
83
+        if (menu->selection < 0) {
84
+#ifdef MENU_PREFER_VOLCANO
85
+            if (dev == DEV_VOLCANO) {
86
+                menu->selection = devs - 1;
87
+            }
88
+#endif // MENU_PREFER_VOLCANO
89
+#ifdef MENU_PREFER_CRAFTY
90
+            if (dev == DEV_CRAFTY) {
91
+                menu->selection = devs - 1;
92
+            }
93
+#endif // MENU_PREFER_CRAFTY
94
+        }
95
+#endif // defined(MENU_PREFER_VOLCANO) || defined(MENU_PREFER_CRAFTY)
96
+
97
+        if ((devs - 1) == menu->selection) {
98
+            pos += snprintf(menu->buff + pos, MENU_MAX_LEN - pos, "> ");
99
+        } else {
100
+            pos += snprintf(menu->buff + pos, MENU_MAX_LEN - pos, "  ");
101
+        }
102
+
103
+        if (dev == DEV_VOLCANO) {
104
+            pos += snprintf(menu->buff + pos, MENU_MAX_LEN - pos, "Volcano ");
105
+        } else if (dev == DEV_CRAFTY) {
106
+            pos += snprintf(menu->buff + pos, MENU_MAX_LEN - pos, "Crafty+ ");
107
+        }
108
+
109
+        char info[32] = "";
110
+        if (models_get_serial(results[i].data, results[i].data_len,
111
+                              info, sizeof(info)) < 0) {
112
+            strcpy(info, "-error-");
113
+        }
114
+        pos += snprintf(menu->buff + pos, MENU_MAX_LEN - pos, "%s\n", info);
115
+    }
116
+
117
+    menu->length = devs;
118
+
119
+#if !defined(MENU_PREFER_VOLCANO) && !defined(MENU_PREFER_CRAFTY)
120
+    if ((menu->selection < 0) && (menu->length > 0)) {
121
+        menu->selection = 0;
122
+    }
123
+#endif // !defined(MENU_PREFER_VOLCANO) && !defined(MENU_PREFER_CRAFTY)
124
+
125
+    if (menu->length == 0) {
126
+        strncpy(menu->buff, "NONE", MENU_MAX_LEN);
127
+    }
128
+}
129
+
130
+void state_scan_run(void) {
131
+    menu_run(draw);
132
+}

+ 146
- 0
src/state_volcano_run.c Bestand weergeven

@@ -0,0 +1,146 @@
1
+/*
2
+ * state_volcano_run.c
3
+ *
4
+ * Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#include <stdio.h>
20
+#include <string.h>
21
+
22
+#include "config.h"
23
+#include "buttons.h"
24
+#include "log.h"
25
+#include "volcano.h"
26
+#include "workflow.h"
27
+#include "state.h"
28
+#include "state_volcano_run.h"
29
+
30
+// only used to draw textbox, not for buttons
31
+#include "menu.h"
32
+
33
+static uint16_t wf_index = 0;
34
+static bd_addr_t ble_addr = {0};
35
+static bd_addr_type_t ble_type = 0;
36
+static bool wait_for_connect = false;
37
+static bool wait_for_disconnect = false;
38
+static bool aborted = false;
39
+
40
+void state_volcano_run_index(uint16_t index) {
41
+    wf_index = index;
42
+}
43
+
44
+void state_volcano_run_target(bd_addr_t addr, bd_addr_type_t type) {
45
+    memcpy(ble_addr, addr, sizeof(bd_addr_t));
46
+    ble_type = type;
47
+}
48
+
49
+static void volcano_buttons(enum buttons btn, bool state) {
50
+    if (state && (btn == BTN_Y)) {
51
+        if ((!wait_for_connect) && (!wait_for_disconnect)) {
52
+            debug("workflow abort");
53
+            aborted = true;
54
+            wf_reset();
55
+            volcano_set_pump_state(false);
56
+            volcano_set_heater_state(false);
57
+            ble_disconnect();
58
+            wait_for_disconnect = true;
59
+        }
60
+    }
61
+}
62
+
63
+void state_volcano_run_enter(void) {
64
+    menu_init(NULL, NULL);
65
+    buttons_callback(volcano_buttons);
66
+
67
+    debug("workflow connect");
68
+    ble_connect(ble_addr, ble_type);
69
+    wait_for_connect = true;
70
+
71
+    aborted = false;
72
+}
73
+
74
+void state_volcano_run_exit(void) {
75
+    menu_deinit();
76
+    wf_reset();
77
+}
78
+
79
+static void draw(struct menu_state *menu) {
80
+    struct wf_state state = wf_status();
81
+
82
+    int pos = 0;
83
+    pos += snprintf(menu->buff + pos, MENU_MAX_LEN - pos,
84
+                    "step %d / %d\n", state.index, state.count);
85
+
86
+    switch (state.step.op) {
87
+    case OP_SET_TEMPERATURE:
88
+        pos += snprintf(menu->buff + pos, MENU_MAX_LEN - pos,
89
+                        "\nset temp %.1f C", state.step.val / 10.0f);
90
+        break;
91
+
92
+    case OP_WAIT_TEMPERATURE:
93
+        pos += snprintf(menu->buff + pos, MENU_MAX_LEN - pos,
94
+                        "wait temp %.1f C\n", state.step.val / 10.0f);
95
+        pos += snprintf(menu->buff + pos, MENU_MAX_LEN - pos,
96
+                        "%.1f -> %.1f -> %.1f",
97
+                        state.start_val / 10.0f,
98
+                        state.curr_val / 10.0f,
99
+                        state.step.val / 10.0f);
100
+        break;
101
+
102
+    case OP_WAIT_TIME:
103
+    case OP_PUMP_TIME:
104
+        pos += snprintf(menu->buff + pos, MENU_MAX_LEN - pos,
105
+                        "%s time %.1f s\n",
106
+                        (state.step.op == OP_WAIT_TIME) ? "wait" : "pump",
107
+                        state.step.val / 1000.0f);
108
+        pos += snprintf(menu->buff + pos, MENU_MAX_LEN - pos,
109
+                        "%.1f -> %.1f -> %.1f",
110
+                        state.start_val / 1000.0f,
111
+                        state.curr_val / 1000.0f,
112
+                        state.step.val / 1000.0f);
113
+        break;
114
+    }
115
+
116
+    // TODO visualize
117
+}
118
+
119
+void state_volcano_run_run(void) {
120
+    // start workflow when connected
121
+    if (wait_for_connect && ble_is_connected()) {
122
+        wait_for_connect = false;
123
+        debug("workflow start");
124
+        wf_start(wf_index);
125
+    }
126
+
127
+    // visualize workflow status
128
+    menu_run(draw);
129
+
130
+    // auto disconnect when end of workflow is reached
131
+    if ((!wait_for_connect) && (!aborted)) {
132
+        struct wf_state state = wf_status();
133
+        if (state.status == WF_IDLE) {
134
+            debug("workflow disconnect");
135
+            ble_disconnect();
136
+            wait_for_disconnect = true;
137
+        }
138
+    }
139
+
140
+    // back to main menu when disconnected
141
+    if (wait_for_disconnect && !ble_is_connected()) {
142
+        wait_for_disconnect = false;
143
+        debug("workflow done");
144
+        state_switch(STATE_SCAN);
145
+    }
146
+}

+ 78
- 0
src/state_volcano_workflow.c Bestand weergeven

@@ -0,0 +1,78 @@
1
+/*
2
+ * state_volcano_workflow.c
3
+ *
4
+ * Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#include <stdio.h>
20
+#include <string.h>
21
+
22
+#include "config.h"
23
+#include "menu.h"
24
+#include "workflow.h"
25
+#include "state.h"
26
+#include "state_volcano_run.h"
27
+#include "state_volcano_workflow.h"
28
+
29
+static void enter_cb(int selection) {
30
+    if ((selection >= 0) && (selection < wf_count())) {
31
+        state_volcano_run_index(selection);
32
+        state_switch(STATE_VOLCANO_RUN);
33
+    }
34
+}
35
+
36
+static void exit_cb(void) {
37
+    state_switch(STATE_SCAN);
38
+}
39
+
40
+void state_volcano_wf_enter(void) {
41
+    menu_init(enter_cb, exit_cb);
42
+}
43
+
44
+void state_volcano_wf_exit(void) {
45
+    menu_deinit();
46
+}
47
+
48
+static void draw(struct menu_state *menu) {
49
+    menu->length = wf_count();
50
+
51
+    int pos = 0;
52
+    for (uint16_t i = 0; i < menu->length; i++) {
53
+        if ((i < menu->off)
54
+            || ((i - menu->off) >= MENU_MAX_LINES)) {
55
+            continue;
56
+        }
57
+
58
+        if (i == menu->selection) {
59
+            pos += snprintf(menu->buff + pos, MENU_MAX_LEN - pos, "> ");
60
+        } else {
61
+            pos += snprintf(menu->buff + pos, MENU_MAX_LEN - pos, "  ");
62
+        }
63
+
64
+        pos += snprintf(menu->buff + pos, MENU_MAX_LEN - pos, "'%s' by %s\n", wf_name(i), wf_author(i));
65
+    }
66
+
67
+    if ((menu->selection < 0) && (menu->length > 0)) {
68
+        menu->selection = 0;
69
+    }
70
+
71
+    if (menu->length == 0) {
72
+        strncpy(menu->buff, "NONE", MENU_MAX_LEN);
73
+    }
74
+}
75
+
76
+void state_volcano_wf_run(void) {
77
+    menu_run(draw);
78
+}

+ 192
- 0
src/text.c Bestand weergeven

@@ -0,0 +1,192 @@
1
+/*
2
+ * text.c
3
+ *
4
+ * Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#include "config.h"
20
+#include "log.h"
21
+#include "lcd.h"
22
+#include "text.h"
23
+
24
+typedef struct {
25
+    struct text_conf *options;
26
+    uint16_t anchor;
27
+} state_t;
28
+
29
+static uint32_t blend(uint32_t fg_c, uint32_t bg_c, uint8_t alpha) {
30
+    float bg[4] = { RGB_565_REV(bg_c), 1.0f };
31
+    float fg[4] = { RGB_565_REV(fg_c), alpha / 255.0f };
32
+    float r[4];
33
+    r[3] = 1.0f - (1.0f - fg[3]) * (1.0f - bg[3]);
34
+    if (r[3] < 1.0e-6f) {
35
+        r[0] = 0.0f;
36
+        r[1] = 0.0f;
37
+        r[2] = 0.0f;
38
+    } else {
39
+        r[0] = fg[0] * fg[3] / r[3] + bg[0] * bg[3] * (1.0f - fg[3]) / r[3];
40
+        r[1] = fg[1] * fg[3] / r[3] + bg[1] * bg[3] * (1.0f - fg[3]) / r[3];
41
+        r[2] = fg[2] * fg[3] / r[3] + bg[2] * bg[3] * (1.0f - fg[3]) / r[3];
42
+    }
43
+
44
+    return RGB_565((uint32_t)(r[0] * 255.0f),
45
+                   (uint32_t)(r[1] * 255.0f),
46
+                   (uint32_t)(r[2] * 255.0f));
47
+}
48
+
49
+static void pixel_callback(int16_t x, int16_t y, uint8_t count, uint8_t alpha,
50
+                           void *state) {
51
+    state_t *s = (state_t*)state;
52
+
53
+    if (y < 0 || y >= s->options->height) return;
54
+    if (x < 0 || x + count >= s->options->width) return;
55
+
56
+    while (count--) {
57
+        lcd_write_point(240 - y - 1, x,
58
+                        blend(s->options->fg, s->options->bg, alpha));
59
+        x++;
60
+    }
61
+}
62
+
63
+static uint8_t character_callback(int16_t x, int16_t y, mf_char character,
64
+                                  void *state) {
65
+    state_t *s = (state_t*)state;
66
+    uint8_t w = mf_render_character(s->options->font->font, x, y, character, pixel_callback, state);
67
+    return w;
68
+}
69
+
70
+static bool line_callback(const char *line, uint16_t count, void *state) {
71
+    state_t *s = (state_t*)state;
72
+
73
+    if (s->options->bg != TEXT_BG_NONE) {
74
+        int16_t width = mf_get_string_width(s->options->font->font, line, count, false) + 2 * s->options->margin;
75
+        int16_t line_height = s->options->font->font->line_height;
76
+
77
+        if (s->options->alignment == MF_ALIGN_LEFT) {
78
+            lcd_write_rect(240 - s->options->y - 1 - line_height,
79
+                           s->options->x,
80
+                           240 - s->options->y - 1,
81
+                           s->options->x + width,
82
+                           s->options->bg);
83
+        } else if (s->options->alignment == MF_ALIGN_CENTER) {
84
+            lcd_write_rect(240 - s->options->y - 1 - line_height,
85
+                           s->options->x + s->options->width / 2 - width / 2,
86
+                           240 - s->options->y - 1,
87
+                           s->options->x + s->options->width / 2 + width / 2,
88
+                           s->options->bg);
89
+        } else if (s->options->alignment == MF_ALIGN_RIGHT) {
90
+            lcd_write_rect(240 - s->options->y - 1 - line_height,
91
+                           s->options->x + s->options->width - width,
92
+                           240 - s->options->y - 1,
93
+                           s->options->x + s->options->width,
94
+                           s->options->bg);
95
+        }
96
+    }
97
+
98
+    if (s->options->justify) {
99
+        mf_render_justified(s->options->font->font, s->anchor + s->options->x, s->options->y,
100
+                            s->options->width - s->options->margin * 2,
101
+                            line, count, character_callback, state);
102
+    } else {
103
+        mf_render_aligned(s->options->font->font, s->anchor + s->options->x, s->options->y,
104
+                          s->options->alignment, line, count,
105
+                          character_callback, state);
106
+    }
107
+
108
+    s->options->y += s->options->font->font->line_height;
109
+    return true;
110
+}
111
+
112
+void text_prepare_font(struct text_font *tf) {
113
+    if (!tf) {
114
+        debug("invalid param");
115
+        return;
116
+    }
117
+
118
+    const struct mf_font_s *font = mf_find_font(tf->fontname);
119
+    if (!font) {
120
+        debug("No such font: %s", tf->fontname);
121
+        return;
122
+    }
123
+
124
+    tf->font = font;
125
+
126
+    // TODO
127
+    //struct mf_scaledfont_s scaledfont;
128
+    //if (tf->scale > 1) {
129
+    //    mf_scale_font(&scaledfont, font, tf->scale, tf->scale);
130
+    //    tf->font = scaledfont.font;
131
+    //}
132
+}
133
+
134
+void text_draw(struct text_conf *tc) {
135
+    if ((!tc) || (!tc->font) || (!tc->font->font)) {
136
+        debug("invalid param");
137
+        return;
138
+    }
139
+
140
+    state_t state;
141
+    state.options = tc;
142
+
143
+    if (tc->alignment == MF_ALIGN_LEFT) {
144
+        state.anchor = tc->margin;
145
+    } else if (tc->alignment == MF_ALIGN_CENTER) {
146
+        state.anchor = tc->width / 2;
147
+    } else if (tc->alignment == MF_ALIGN_RIGHT) {
148
+        state.anchor = tc->width - tc->margin;
149
+    }
150
+
151
+    mf_wordwrap(tc->font->font, tc->width - 2 * tc->margin,
152
+                tc->text, line_callback, &state);
153
+}
154
+
155
+void text_box(const char *s) {
156
+    static struct text_font font = {
157
+        .fontname = "fixed_10x20",
158
+        .font = NULL,
159
+    };
160
+    if (font.font == NULL) {
161
+        text_prepare_font(&font);
162
+    }
163
+
164
+    int x = 0;
165
+    int width = 240;
166
+
167
+    int y = 50;
168
+    int height = 120;
169
+
170
+    struct text_conf text = {
171
+        .text = "",
172
+        .x = x,
173
+        .y = y,
174
+        .justify = false,
175
+        .alignment = MF_ALIGN_CENTER,
176
+        .width = width,
177
+        .height = height,
178
+        .margin = 2,
179
+        .fg = RGB_565(0xFF, 0xFF, 0xFF),
180
+        .bg = RGB_565(0x00, 0x00, 0x00),
181
+        .font = &font,
182
+    };
183
+
184
+    lcd_write_rect(240 - y - 1 - height,
185
+                   x,
186
+                   240 - y - 1,
187
+                   x + width - 1,
188
+                   text.bg);
189
+
190
+    text.text = s;
191
+    text_draw(&text);
192
+}

+ 70
- 0
src/usb.c Bestand weergeven

@@ -0,0 +1,70 @@
1
+/*
2
+ * Extended from TinyUSB example code.
3
+ *
4
+ * Copyright (c) 2022 - 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * The MIT License (MIT)
7
+ *
8
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
9
+ *
10
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ * of this software and associated documentation files (the "Software"), to deal
12
+ * in the Software without restriction, including without limitation the rights
13
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ * copies of the Software, and to permit persons to whom the Software is
15
+ * furnished to do so, subject to the following conditions:
16
+ *
17
+ * The above copyright notice and this permission notice shall be included in
18
+ * all copies or substantial portions of the Software.
19
+ *
20
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26
+ * THE SOFTWARE.
27
+ *
28
+ */
29
+
30
+#include "bsp/board.h"
31
+#include "tusb.h"
32
+
33
+#include "config.h"
34
+#include "log.h"
35
+#include "usb_descriptors.h"
36
+#include "usb_cdc.h"
37
+#include "usb.h"
38
+
39
+void usb_init(void) {
40
+    usb_descriptor_init_id();
41
+
42
+    board_init();
43
+    tusb_init();
44
+}
45
+
46
+void usb_run(void) {
47
+    tud_task();
48
+}
49
+
50
+// Invoked when device is mounted
51
+void tud_mount_cb(void) {
52
+    debug("device mounted");
53
+}
54
+
55
+// Invoked when device is unmounted
56
+void tud_umount_cb(void) {
57
+    debug("device unmounted");
58
+}
59
+
60
+// Invoked when usb bus is suspended
61
+// remote_wakeup_en : if host allow us  to perform remote wakeup
62
+// Within 7ms, device must draw an average of current less than 2.5 mA from bus
63
+void tud_suspend_cb(bool remote_wakeup_en) {
64
+    debug("device suspended wakeup=%d", remote_wakeup_en);
65
+}
66
+
67
+// Invoked when usb bus is resumed
68
+void tud_resume_cb(void) {
69
+    debug("device resumed");
70
+}

+ 115
- 0
src/usb_cdc.c Bestand weergeven

@@ -0,0 +1,115 @@
1
+/*
2
+ * Extended from TinyUSB example code.
3
+ *
4
+ * Copyright (c) 2022 - 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * The MIT License (MIT)
7
+ *
8
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
9
+ *
10
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ * of this software and associated documentation files (the "Software"), to deal
12
+ * in the Software without restriction, including without limitation the rights
13
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ * copies of the Software, and to permit persons to whom the Software is
15
+ * furnished to do so, subject to the following conditions:
16
+ *
17
+ * The above copyright notice and this permission notice shall be included in
18
+ * all copies or substantial portions of the Software.
19
+ *
20
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26
+ * THE SOFTWARE.
27
+ *
28
+ */
29
+
30
+#include "bsp/board.h"
31
+#include "tusb.h"
32
+
33
+#include "config.h"
34
+#include "console.h"
35
+#include "log.h"
36
+#include "util.h"
37
+#include "usb_descriptors.h"
38
+#include "usb_cdc.h"
39
+
40
+static bool reroute_cdc_debug = false;
41
+
42
+void usb_cdc_write(const uint8_t *buf, size_t count) {
43
+#ifndef DISABLE_CDC_DTR_CHECK
44
+    if (!tud_cdc_connected()) {
45
+        return;
46
+    }
47
+#endif // DISABLE_CDC_DTR_CHECK
48
+
49
+    // implemented similar to Pico SDK stdio usb
50
+    uint32_t len = 0;
51
+    while (len < count) {
52
+        uint32_t n = count - len;
53
+        uint32_t available = tud_cdc_write_available();
54
+
55
+        // only write as much as possible
56
+        if (n > available) {
57
+            n = available;
58
+        }
59
+
60
+        len += tud_cdc_write(buf + len, n);
61
+
62
+        // run tud_task to actually move stuff from FIFO
63
+        tud_task();
64
+        tud_cdc_write_flush();
65
+    }
66
+}
67
+
68
+void usb_cdc_set_reroute(bool reroute) {
69
+    reroute_cdc_debug = reroute;
70
+}
71
+
72
+static void cdc_task(void) {
73
+    const uint32_t cdc_buf_len = 64;
74
+
75
+    if (tud_cdc_available()) {
76
+        char buf[cdc_buf_len + 1];
77
+        uint32_t count = tud_cdc_read(buf, cdc_buf_len);
78
+
79
+        if ((count >= 1) && (buf[0] == ENTER_BOOTLOADER_MAGIC)) {
80
+            reset_to_bootloader();
81
+        } else if (reroute_cdc_debug) {
82
+            debug_handle_input((const uint8_t *)buf, count);
83
+        } else {
84
+            cnsl_handle_input((const uint8_t *)buf, count);
85
+        }
86
+    }
87
+}
88
+
89
+// invoked when cdc when line state changed e.g connected/disconnected
90
+void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts) {
91
+    (void) itf;
92
+    (void) rts;
93
+
94
+    static bool last_dtr = false;
95
+
96
+    if (dtr && !last_dtr) {
97
+        // clear left-over console input
98
+        cnsl_init();
99
+
100
+        // show past history
101
+        log_dump_to_usb();
102
+
103
+        debug("terminal connected");
104
+    } else if (!dtr && last_dtr) {
105
+        debug("terminal disconnected");
106
+    }
107
+
108
+    last_dtr = dtr;
109
+}
110
+
111
+// invoked when CDC interface received data from host
112
+void tud_cdc_rx_cb(uint8_t itf) {
113
+    (void) itf;
114
+    cdc_task();
115
+}

+ 229
- 0
src/usb_descriptors.c Bestand weergeven

@@ -0,0 +1,229 @@
1
+/*
2
+ * Extended from TinyUSB example code.
3
+ *
4
+ * Copyright (c) 2022 - 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * The MIT License (MIT)
7
+ *
8
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
9
+ *
10
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ * of this software and associated documentation files (the "Software"), to deal
12
+ * in the Software without restriction, including without limitation the rights
13
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ * copies of the Software, and to permit persons to whom the Software is
15
+ * furnished to do so, subject to the following conditions:
16
+ *
17
+ * The above copyright notice and this permission notice shall be included in
18
+ * all copies or substantial portions of the Software.
19
+ *
20
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26
+ * THE SOFTWARE.
27
+ *
28
+ */
29
+
30
+#include "pico/unique_id.h"
31
+#include "tusb.h"
32
+
33
+#include "config.h"
34
+#include "usb_descriptors.h"
35
+
36
+/*
37
+ * A combination of interfaces must have a unique product id,
38
+ * since PC will save device driver after the first plug.
39
+ * Same VID/PID with different interface e.g MSC (first),
40
+ * then CDC (later) will possibly cause system error on PC.
41
+ *
42
+ * Auto ProductID layout's Bitmap:
43
+ *   [MSB]         HID | MSC | CDC          [LSB]
44
+ */
45
+#define _PID_MAP(itf, n) ((CFG_TUD_##itf) << (n))
46
+#define USB_PID (0x2300 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) \
47
+    | _PID_MAP(HID, 2) | _PID_MAP(MIDI, 3) | _PID_MAP(VENDOR, 4) )
48
+
49
+#define USB_VID   0xCafe
50
+#define USB_BCD   0x0200
51
+
52
+//--------------------------------------------------------------------+
53
+// Device Descriptors
54
+//--------------------------------------------------------------------+
55
+
56
+tusb_desc_device_t const desc_device =
57
+{
58
+    .bLength            = sizeof(tusb_desc_device_t),
59
+    .bDescriptorType    = TUSB_DESC_DEVICE,
60
+    .bcdUSB             = USB_BCD,
61
+
62
+    /*
63
+     * Use Interface Association Descriptor (IAD) for CDC
64
+     * As required by USB Specs IAD's subclass must be common class (2)
65
+     * and protocol must be IAD (1)
66
+     */
67
+    .bDeviceClass       = TUSB_CLASS_MISC,
68
+    .bDeviceSubClass    = MISC_SUBCLASS_COMMON,
69
+    .bDeviceProtocol    = MISC_PROTOCOL_IAD,
70
+
71
+    .bMaxPacketSize0    = CFG_TUD_ENDPOINT0_SIZE,
72
+
73
+    .idVendor           = USB_VID,
74
+    .idProduct          = USB_PID,
75
+    .bcdDevice          = 0x0100,
76
+
77
+    .iManufacturer      = 0x01,
78
+    .iProduct           = 0x02,
79
+    .iSerialNumber      = 0x03,
80
+
81
+    .bNumConfigurations = 0x01
82
+};
83
+
84
+// Invoked when received GET DEVICE DESCRIPTOR
85
+// Application return pointer to descriptor
86
+uint8_t const * tud_descriptor_device_cb(void) {
87
+    return (uint8_t const *) &desc_device;
88
+}
89
+
90
+//--------------------------------------------------------------------+
91
+// Configuration Descriptor
92
+//--------------------------------------------------------------------+
93
+
94
+enum {
95
+    ITF_NUM_CDC = 0,
96
+    ITF_NUM_CDC_DATA,
97
+    ITF_NUM_MSC,
98
+    ITF_NUM_TOTAL
99
+};
100
+
101
+#define  CONFIG_TOTAL_LEN  (TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN + TUD_MSC_DESC_LEN)
102
+
103
+#define EPNUM_CDC_NOTIF 0x82
104
+#define EPNUM_CDC_OUT   0x02
105
+#define EPNUM_CDC_IN    0x83
106
+#define EPNUM_MSC_OUT   0x03
107
+#define EPNUM_MSC_IN    0x84
108
+
109
+uint8_t const desc_configuration[] = {
110
+    // Config number, interface count, string index, total length, attribute, power in mA
111
+    TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
112
+
113
+    // Interface number, string index, EP notification address and size, EP data address (out, in) and size.
114
+    TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, 4, EPNUM_CDC_NOTIF, 8, EPNUM_CDC_OUT, EPNUM_CDC_IN, 64),
115
+
116
+    // Interface number, string index, EP Out & EP In address, EP size
117
+    TUD_MSC_DESCRIPTOR(ITF_NUM_MSC, 5, EPNUM_MSC_OUT, EPNUM_MSC_IN, 64),
118
+};
119
+
120
+#if TUD_OPT_HIGH_SPEED
121
+// Per USB specs: high speed capable device must report device_qualifier and other_speed_configuration
122
+
123
+// other speed configuration
124
+uint8_t desc_other_speed_config[CONFIG_TOTAL_LEN];
125
+
126
+// device qualifier is mostly similar to device descriptor since we don't change configuration based on speed
127
+tusb_desc_device_qualifier_t const desc_device_qualifier = {
128
+    .bLength            = sizeof(tusb_desc_device_qualifier_t),
129
+    .bDescriptorType    = TUSB_DESC_DEVICE_QUALIFIER,
130
+    .bcdUSB             = USB_BCD,
131
+
132
+    .bDeviceClass       = TUSB_CLASS_MISC,
133
+    .bDeviceSubClass    = MISC_SUBCLASS_COMMON,
134
+    .bDeviceProtocol    = MISC_PROTOCOL_IAD,
135
+
136
+    .bMaxPacketSize0    = CFG_TUD_ENDPOINT0_SIZE,
137
+    .bNumConfigurations = 0x01,
138
+    .bReserved          = 0x00
139
+};
140
+
141
+// Invoked when received GET DEVICE QUALIFIER DESCRIPTOR request
142
+// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete.
143
+// device_qualifier descriptor describes information about a high-speed capable device that would
144
+// change if the device were operating at the other speed. If not highspeed capable stall this request.
145
+uint8_t const* tud_descriptor_device_qualifier_cb(void) {
146
+    return (uint8_t const*) &desc_device_qualifier;
147
+}
148
+
149
+// Invoked when received GET OTHER SEED CONFIGURATION DESCRIPTOR request
150
+// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete
151
+// Configuration descriptor in the other speed e.g if high speed then this is for full speed and vice versa
152
+uint8_t const* tud_descriptor_other_speed_configuration_cb(uint8_t index) {
153
+    (void) index; // for multiple configurations
154
+
155
+    // other speed config is basically configuration with type = OHER_SPEED_CONFIG
156
+    memcpy(desc_other_speed_config, desc_configuration, CONFIG_TOTAL_LEN);
157
+    desc_other_speed_config[1] = TUSB_DESC_OTHER_SPEED_CONFIG;
158
+
159
+    // this example use the same configuration for both high and full speed mode
160
+    return desc_other_speed_config;
161
+}
162
+
163
+#endif // highspeed
164
+
165
+// Invoked when received GET CONFIGURATION DESCRIPTOR
166
+// Application return pointer to descriptor
167
+// Descriptor contents must exist long enough for transfer to complete
168
+uint8_t const * tud_descriptor_configuration_cb(uint8_t index) {
169
+    (void) index; // for multiple configurations
170
+
171
+    // This example use the same configuration for both high and full speed mode
172
+    return desc_configuration;
173
+}
174
+
175
+//--------------------------------------------------------------------+
176
+// String Descriptors
177
+//--------------------------------------------------------------------+
178
+
179
+char string_pico_serial[2 * PICO_UNIQUE_BOARD_ID_SIZE_BYTES + 1];
180
+
181
+void usb_descriptor_init_id(void) {
182
+    pico_get_unique_board_id_string(string_pico_serial, sizeof(string_pico_serial));
183
+}
184
+
185
+// array of pointer to string descriptors
186
+char const* string_desc_arr [] = {
187
+    (const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409)
188
+    "xythobuz",                    // 1: Manufacturer
189
+    "VolcanoRC",                   // 2: Product
190
+    string_pico_serial,            // 3: Serials, should use chip ID
191
+    "Debug Serial",                // 4: CDC Interface
192
+    "Debug Memory",                // 5: MSC Interface
193
+};
194
+
195
+static uint16_t _desc_str[32];
196
+
197
+// Invoked when received GET STRING DESCRIPTOR request
198
+// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete
199
+uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid) {
200
+    (void) langid;
201
+
202
+    uint8_t chr_count;
203
+
204
+    if ( index == 0) {
205
+        memcpy(&_desc_str[1], string_desc_arr[0], 2);
206
+        chr_count = 1;
207
+    } else {
208
+        // Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors.
209
+        // https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors
210
+
211
+        if ( !(index < sizeof(string_desc_arr)/sizeof(string_desc_arr[0])) ) return NULL;
212
+
213
+        const char* str = string_desc_arr[index];
214
+
215
+        // Cap at max char
216
+        chr_count = strlen(str);
217
+        if ( chr_count > 31 ) chr_count = 31;
218
+
219
+        // Convert ASCII string into UTF-16
220
+        for(uint8_t i=0; i<chr_count; i++) {
221
+            _desc_str[1+i] = str[i];
222
+        }
223
+    }
224
+
225
+    // first byte is length (including header), second byte is string type
226
+    _desc_str[0] = (TUSB_DESC_STRING << 8 ) | (2*chr_count + 2);
227
+
228
+    return _desc_str;
229
+}

+ 215
- 0
src/usb_msc.c Bestand weergeven

@@ -0,0 +1,215 @@
1
+/*
2
+ * Extended from TinyUSB example code.
3
+ *
4
+ * Copyright (c) 2022 - 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * The MIT License (MIT)
7
+ *
8
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
9
+ *
10
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ * of this software and associated documentation files (the "Software"), to deal
12
+ * in the Software without restriction, including without limitation the rights
13
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ * copies of the Software, and to permit persons to whom the Software is
15
+ * furnished to do so, subject to the following conditions:
16
+ *
17
+ * The above copyright notice and this permission notice shall be included in
18
+ * all copies or substantial portions of the Software.
19
+ *
20
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26
+ * THE SOFTWARE.
27
+ */
28
+
29
+#include "bsp/board.h"
30
+#include "tusb.h"
31
+
32
+#include "config.h"
33
+#include "fat_disk.h"
34
+#include "log.h"
35
+
36
+static bool ejected = false;
37
+static bool medium_available = false;
38
+
39
+bool msc_is_medium_available(void) {
40
+    return medium_available;
41
+}
42
+
43
+void msc_set_medium_available(bool state) {
44
+    medium_available = state;
45
+    if (state) {
46
+        ejected = false;
47
+    }
48
+}
49
+
50
+// Invoked when received SCSI_CMD_INQUIRY
51
+// Application fill vendor id, product id and revision
52
+// with string up to 8, 16, 4 characters respectively
53
+void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8],
54
+        uint8_t product_id[16], uint8_t product_rev[4]) {
55
+    (void) lun;
56
+
57
+    const char vid[] = "xythobuz";
58
+    const char pid[] = "Debug Storage";
59
+    const char rev[] = "0.01";
60
+
61
+    memcpy(vendor_id  , vid, strlen(vid));
62
+    memcpy(product_id , pid, strlen(pid));
63
+    memcpy(product_rev, rev, strlen(rev));
64
+}
65
+
66
+// Invoked when received Test Unit Ready command.
67
+// return true allowing host to read/write this LUN e.g SD card inserted
68
+bool tud_msc_test_unit_ready_cb(uint8_t lun) {
69
+    if (!medium_available) {
70
+        // Additional Sense 3A-00 is NOT_FOUND
71
+        tud_msc_set_sense(lun, SCSI_SENSE_NOT_READY, 0x3a, 0x00);
72
+    }
73
+
74
+    return medium_available;
75
+}
76
+
77
+// Invoked when received SCSI_CMD_READ_CAPACITY_10 and
78
+// SCSI_CMD_READ_FORMAT_CAPACITY to determine the disk size
79
+// Application update block count and block size
80
+void tud_msc_capacity_cb(uint8_t lun, uint32_t* block_count, uint16_t* block_size) {
81
+    (void) lun;
82
+
83
+    if (!medium_available) {
84
+        *block_count = 0;
85
+        *block_size = 0;
86
+    } else {
87
+        *block_count = DISK_BLOCK_COUNT;
88
+        *block_size  = DISK_BLOCK_SIZE;
89
+    }
90
+}
91
+
92
+// Invoked when received Start Stop Unit command
93
+// - Start = 0 : stopped power mode, if load_eject = 1 : unload disk storage
94
+// - Start = 1 : active mode, if load_eject = 1 : load disk storage
95
+bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition,
96
+        bool start, bool load_eject) {
97
+    (void) lun;
98
+    (void) power_condition;
99
+
100
+    if (start) {
101
+        // load disk storage
102
+        debug("load disk storage %d", load_eject);
103
+    } else {
104
+        // unload disk storage
105
+        debug("unload disk storage %d", load_eject);
106
+        if (load_eject) {
107
+            medium_available = false;
108
+        }
109
+    }
110
+
111
+    return true;
112
+}
113
+
114
+// Callback invoked when received READ10 command.
115
+// Copy disk's data to buffer (up to bufsize) and return number of copied bytes.
116
+int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba,
117
+        uint32_t offset, void* buffer, uint32_t bufsize) {
118
+    (void) lun;
119
+
120
+    // out of ramdisk
121
+    if (lba >= DISK_BLOCK_COUNT) {
122
+        return -1;
123
+    }
124
+
125
+    // TODO better range checking and length calculation?
126
+
127
+    uint8_t const* addr = fat_disk_get_sector(lba) + offset;
128
+    memcpy(buffer, addr, bufsize);
129
+
130
+    return (int32_t) bufsize;
131
+}
132
+
133
+bool tud_msc_is_writable_cb (uint8_t lun) {
134
+    (void) lun;
135
+
136
+    return true;
137
+}
138
+
139
+// Callback invoked when received WRITE10 command.
140
+// Process data in buffer to disk's storage and return number of written bytes
141
+int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba,
142
+        uint32_t offset, uint8_t* buffer, uint32_t bufsize) {
143
+    (void) lun;
144
+
145
+    // out of ramdisk
146
+    if (lba >= DISK_BLOCK_COUNT) {
147
+        return -1;
148
+    }
149
+
150
+    // TODO better range checking and length calculation?
151
+
152
+    uint8_t* addr = fat_disk_get_sector(lba) + offset;
153
+    memcpy(addr, buffer, bufsize);
154
+
155
+    return (int32_t) bufsize;
156
+}
157
+
158
+// Callback invoked when received an SCSI command not in built-in list below
159
+// - READ_CAPACITY10, READ_FORMAT_CAPACITY, INQUIRY, MODE_SENSE6, REQUEST_SENSE
160
+// - READ10 and WRITE10 has their own callbacks and MUST not be handled here
161
+int32_t tud_msc_scsi_cb (uint8_t lun, uint8_t const scsi_cmd[16],
162
+        void* buffer, uint16_t bufsize) {
163
+    void const* response = NULL;
164
+    int32_t resplen = 0;
165
+
166
+    // most scsi handled is input
167
+    bool in_xfer = true;
168
+
169
+    switch (scsi_cmd[0]) {
170
+    case 0x1E:
171
+        // Prevent/Allow Medium Removal
172
+        if (scsi_cmd[4] & 0x01) {
173
+            // Prevent medium removal
174
+            if (!medium_available) {
175
+                debug("Host wants to lock non-existing medium. Not supported.");
176
+                resplen = -1;
177
+            } else {
178
+                debug("Host wants to lock medium.");
179
+            }
180
+        } else {
181
+            // Allow medium removal
182
+            if (medium_available) {
183
+                debug("Host ejected medium. Unplugging disk.");
184
+                medium_available = false;
185
+            } else {
186
+                debug("host ejected non-existing medium. Not supported.");
187
+                resplen = -1;
188
+            }
189
+        }
190
+        break;
191
+
192
+    default:
193
+        // Set Sense = Invalid Command Operation
194
+        tud_msc_set_sense(lun, SCSI_SENSE_ILLEGAL_REQUEST, 0x20, 0x00);
195
+
196
+        // negative means error -> tinyusb could stall and/or response with failed status
197
+        resplen = -1;
198
+        break;
199
+    }
200
+
201
+    // return resplen must not larger than bufsize
202
+    if (resplen > bufsize) {
203
+        resplen = bufsize;
204
+    }
205
+
206
+    if (response && (resplen > 0)) {
207
+        if (in_xfer) {
208
+            memcpy(buffer, response, (size_t) resplen);
209
+        } else {
210
+            // SCSI output
211
+        }
212
+    }
213
+
214
+    return (int32_t) resplen;
215
+}

+ 109
- 0
src/util.c Bestand weergeven

@@ -0,0 +1,109 @@
1
+/*
2
+ * util.c
3
+ *
4
+ * Copyright (c) 2022 - 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#include <string.h>
20
+#include "pico/stdlib.h"
21
+#include "pico/bootrom.h"
22
+#include "hardware/watchdog.h"
23
+
24
+#ifdef CYW43_WL_GPIO_LED_PIN
25
+#include "pico/cyw43_arch.h"
26
+#endif // CYW43_WL_GPIO_LED_PIN
27
+
28
+#include "config.h"
29
+#include "log.h"
30
+#include "util.h"
31
+
32
+#define HEARTBEAT_INTERVAL_MS 500
33
+
34
+void heartbeat_init(void) {
35
+#ifdef PICO_DEFAULT_LED_PIN
36
+    gpio_init(PICO_DEFAULT_LED_PIN);
37
+    gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT);
38
+    gpio_put(PICO_DEFAULT_LED_PIN, 1);
39
+#endif // PICO_DEFAULT_LED_PIN
40
+#ifdef CYW43_WL_GPIO_LED_PIN
41
+    cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 1);
42
+#endif // CYW43_WL_GPIO_LED_PIN
43
+}
44
+
45
+void heartbeat_run(void) {
46
+#if defined(PICO_DEFAULT_LED_PIN) || defined(CYW43_WL_GPIO_LED_PIN)
47
+    static uint32_t last_heartbeat = 0;
48
+    uint32_t now = to_ms_since_boot(get_absolute_time());
49
+    if (now >= (last_heartbeat + HEARTBEAT_INTERVAL_MS)) {
50
+        last_heartbeat = now;
51
+#ifdef PICO_DEFAULT_LED_PIN
52
+        gpio_xor_mask(1 << PICO_DEFAULT_LED_PIN);
53
+#endif // PICO_DEFAULT_LED_PIN
54
+#ifdef CYW43_WL_GPIO_LED_PIN
55
+        cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, !cyw43_arch_gpio_get(CYW43_WL_GPIO_LED_PIN));
56
+#endif // CYW43_WL_GPIO_LED_PIN
57
+    }
58
+#endif // defined(PICO_DEFAULT_LED_PIN) || defined(CYW43_WL_GPIO_LED_PIN)
59
+}
60
+
61
+bool str_startswith(const char *str, const char *start) {
62
+    size_t l = strlen(start);
63
+    if (l > strlen(str)) {
64
+        return false;
65
+    }
66
+    return (strncmp(str, start, l) == 0);
67
+}
68
+
69
+int32_t convert_two_complement(int32_t b) {
70
+    if (b & 0x8000) {
71
+        b = -1 * ((b ^ 0xffff) + 1);
72
+    }
73
+    return b;
74
+}
75
+
76
+void reset_to_bootloader(void) {
77
+#ifdef PICO_DEFAULT_LED_PIN
78
+    reset_usb_boot(1 << PICO_DEFAULT_LED_PIN, 0);
79
+#else // ! PICO_DEFAULT_LED_PIN
80
+    reset_usb_boot(0, 0);
81
+#endif // PICO_DEFAULT_LED_PIN
82
+}
83
+
84
+void reset_to_main(void) {
85
+    watchdog_enable(1, 1);
86
+    while (1) {
87
+        // wait 1ms until watchdog kills us
88
+        asm volatile("nop");
89
+    }
90
+}
91
+
92
+void hexdump(const uint8_t *buff, size_t len) {
93
+    for (size_t i = 0; i < len; i += 16) {
94
+        for (size_t j = 0; (j < 16) && ((i + j) < len); j++) {
95
+            print("0x%02X", buff[i + j]);
96
+            if ((j < 15) && ((i + j) < (len - 1))) {
97
+                print(" ");
98
+            }
99
+        }
100
+        println();
101
+    }
102
+}
103
+
104
+float map(float value, float leftMin, float leftMax, float rightMin, float rightMax) {
105
+    float leftSpan = leftMax - leftMin;
106
+    float rightSpan = rightMax - rightMin;
107
+    float valueScaled = (value - leftMin) / leftSpan;
108
+    return rightMin + (valueScaled * rightSpan);
109
+}

+ 118
- 0
src/volcano.c Bestand weergeven

@@ -0,0 +1,118 @@
1
+/*
2
+ * volcano.c
3
+ *
4
+ * Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#include "config.h"
20
+#include "log.h"
21
+#include "ble.h"
22
+#include "volcano.h"
23
+
24
+// Volcano UUIDs are always the same, except for the 4th byte
25
+#define UUID_WRITE_SRVC   0x00
26
+#define UUID_CURRENT_TEMP 0x01
27
+#define UUID_TARGET_TEMP  0x03
28
+#define UUID_HEATER_ON    0x0F
29
+#define UUID_HEATER_OFF   0x10
30
+#define UUID_PUMP_ON      0x13
31
+#define UUID_PUMP_OFF     0x14
32
+
33
+// "101100xx-5354-4f52-5a26-4249434b454c"
34
+static uint8_t uuid_base[16] = {
35
+    0x10, 0x11, 0x00, 0xFF, 0x53, 0x54, 0x4f, 0x52,
36
+    0x5a, 0x26, 0x42, 0x49, 0x43, 0x4b, 0x45, 0x4c,
37
+};
38
+static uint8_t uuid_base2[16] = {
39
+    0x10, 0x11, 0x00, 0xFF, 0x53, 0x54, 0x4f, 0x52,
40
+    0x5a, 0x26, 0x42, 0x49, 0x43, 0x4b, 0x45, 0x4c,
41
+};
42
+
43
+int16_t volcano_get_current_temp(void) {
44
+    uuid_base[3] = UUID_CURRENT_TEMP;
45
+
46
+    uint8_t buff[4];
47
+    int32_t r = ble_read(uuid_base, buff, sizeof(buff));
48
+    if (r != sizeof(buff)) {
49
+        debug("ble_read unexpected value %ld", r);
50
+        return -1;
51
+    }
52
+
53
+    uint32_t *v = (uint32_t *)buff;
54
+    return *v;
55
+}
56
+
57
+int16_t volcano_get_target_temp(void) {
58
+    uuid_base[3] = UUID_TARGET_TEMP;
59
+
60
+    uint8_t buff[4];
61
+    int32_t r = ble_read(uuid_base, buff, sizeof(buff));
62
+    if (r != sizeof(buff)) {
63
+        debug("ble_read unexpected value %ld", r);
64
+        return -1;
65
+    }
66
+
67
+    uint32_t *v = (uint32_t *)buff;
68
+    return *v;
69
+}
70
+
71
+int8_t volcano_set_target_temp(uint16_t value) {
72
+    uuid_base[3] = UUID_WRITE_SRVC;
73
+    uuid_base2[3] = UUID_TARGET_TEMP;
74
+
75
+    uint8_t buff[4];
76
+    uint32_t *v = (uint32_t *)buff;
77
+    *v = value;
78
+
79
+    int8_t r = ble_write(uuid_base, uuid_base2, buff, sizeof(buff));
80
+    if (r != 0) {
81
+        debug("ble_write unexpected value %d", r);
82
+    }
83
+    return r;
84
+}
85
+
86
+int8_t volcano_set_heater_state(bool value) {
87
+    uuid_base[3] = UUID_WRITE_SRVC;
88
+
89
+    if (value) {
90
+        uuid_base2[3] = UUID_HEATER_ON;
91
+    } else {
92
+        uuid_base2[3] = UUID_HEATER_OFF;
93
+    }
94
+
95
+    uint8_t d = 0;
96
+    int8_t r = ble_write(uuid_base, uuid_base2, &d, sizeof(d));
97
+    if (r != 0) {
98
+        debug("ble_write unexpected value %d", r);
99
+    }
100
+    return r;
101
+}
102
+
103
+int8_t volcano_set_pump_state(bool value) {
104
+    uuid_base[3] = UUID_WRITE_SRVC;
105
+
106
+    if (value) {
107
+        uuid_base2[3] = UUID_PUMP_ON;
108
+    } else {
109
+        uuid_base2[3] = UUID_PUMP_OFF;
110
+    }
111
+
112
+    uint8_t d = 0;
113
+    int8_t r = ble_write(uuid_base, uuid_base2, &d, sizeof(d));
114
+    if (r != 0) {
115
+        debug("ble_write unexpected value %d", r);
116
+    }
117
+    return r;
118
+}

+ 264
- 0
src/workflow.c Bestand weergeven

@@ -0,0 +1,264 @@
1
+/*
2
+ * workflow.c
3
+ *
4
+ * Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#include "config.h"
20
+#include "log.h"
21
+#include "volcano.h"
22
+#include "workflow.h"
23
+
24
+#define WF_MAX_STEPS 42
25
+#define WF_MAX_FLOWS 6
26
+
27
+struct workflow {
28
+    const char *name;
29
+    const char *author;
30
+    struct wf_step steps[WF_MAX_STEPS];
31
+    uint16_t count;
32
+};
33
+
34
+#define NOTIFY \
35
+    { .op = OP_WAIT_TIME, .val = 1000 }, \
36
+    { .op = OP_PUMP_TIME, .val = 1000 }
37
+
38
+static const struct workflow wf[WF_MAX_FLOWS] = {
39
+    {
40
+        .name = "Default",
41
+        .author = "xythobuz",
42
+        .steps = {
43
+            { .op = OP_WAIT_TEMPERATURE, .val = 1850 },
44
+            { .op = OP_WAIT_TIME, .val = 10000 },
45
+            { .op = OP_PUMP_TIME, .val = 5000 },
46
+
47
+            { .op = OP_WAIT_TEMPERATURE, .val = 1950 },
48
+            { .op = OP_WAIT_TIME, .val = 5000 },
49
+            { .op = OP_PUMP_TIME, .val = 20000 },
50
+
51
+            { .op = OP_WAIT_TEMPERATURE, .val = 2050 },
52
+            { .op = OP_WAIT_TIME, .val = 5000 },
53
+            { .op = OP_PUMP_TIME, .val = 20000 },
54
+
55
+            NOTIFY, NOTIFY, NOTIFY, NOTIFY,
56
+
57
+            { .op = OP_SET_TEMPERATURE, .val = 1900 },
58
+        },
59
+        .count = 18,
60
+    }, {
61
+        .name = "Vorbi",
62
+        .author = "Rinor",
63
+        .steps = {
64
+            { .op = OP_WAIT_TEMPERATURE, .val = 1760 },
65
+            { .op = OP_WAIT_TIME, .val = 10000 },
66
+            { .op = OP_PUMP_TIME, .val = 6000 },
67
+
68
+            { .op = OP_WAIT_TEMPERATURE, .val = 1870 },
69
+            { .op = OP_WAIT_TIME, .val = 5000 },
70
+            { .op = OP_PUMP_TIME, .val = 10000 },
71
+
72
+            { .op = OP_WAIT_TEMPERATURE, .val = 2040 },
73
+            { .op = OP_WAIT_TIME, .val = 3000 },
74
+            { .op = OP_PUMP_TIME, .val = 10000 },
75
+
76
+            { .op = OP_WAIT_TEMPERATURE, .val = 2170 },
77
+            { .op = OP_WAIT_TIME, .val = 5000 },
78
+            { .op = OP_PUMP_TIME, .val = 10000 },
79
+        },
80
+        .count = 12,
81
+    }, {
82
+        .name = "Relaxo",
83
+        .author = "xythobuz",
84
+        .steps = {
85
+            { .op = OP_WAIT_TEMPERATURE, .val = 1750 },
86
+            { .op = OP_WAIT_TIME, .val = 10000 },
87
+            { .op = OP_PUMP_TIME, .val = 5000 },
88
+
89
+            { .op = OP_WAIT_TEMPERATURE, .val = 1850 },
90
+            { .op = OP_WAIT_TIME, .val = 5000 },
91
+            { .op = OP_PUMP_TIME, .val = 20000 },
92
+
93
+            { .op = OP_WAIT_TEMPERATURE, .val = 1950 },
94
+            { .op = OP_WAIT_TIME, .val = 5000 },
95
+            { .op = OP_PUMP_TIME, .val = 20000 },
96
+
97
+            NOTIFY, NOTIFY, NOTIFY, NOTIFY,
98
+
99
+            { .op = OP_SET_TEMPERATURE, .val = 1900 },
100
+        },
101
+        .count = 18,
102
+    }, {
103
+        .name = "Hotty",
104
+        .author = "xythobuz",
105
+        .steps = {
106
+            { .op = OP_WAIT_TEMPERATURE, .val = 1900 },
107
+            { .op = OP_WAIT_TIME, .val = 10000 },
108
+            { .op = OP_PUMP_TIME, .val = 5000 },
109
+
110
+            { .op = OP_WAIT_TEMPERATURE, .val = 2050 },
111
+            { .op = OP_WAIT_TIME, .val = 5000 },
112
+            { .op = OP_PUMP_TIME, .val = 20000 },
113
+
114
+            { .op = OP_WAIT_TEMPERATURE, .val = 2200 },
115
+            { .op = OP_WAIT_TIME, .val = 5000 },
116
+            { .op = OP_PUMP_TIME, .val = 20000 },
117
+
118
+            NOTIFY, NOTIFY, NOTIFY, NOTIFY,
119
+
120
+            { .op = OP_SET_TEMPERATURE, .val = 1900 },
121
+        },
122
+        .count = 18,
123
+    },
124
+};
125
+
126
+static const uint16_t count = 4;
127
+
128
+static enum wf_status status = WF_IDLE;
129
+static uint16_t wf_i = 0;
130
+static uint16_t step = 0;
131
+static uint32_t start_t = 0;
132
+static uint16_t start_val = 0;
133
+static uint16_t curr_val = 0;
134
+
135
+static void do_step(void) {
136
+    switch (wf[wf_i].steps[step].op) {
137
+    case OP_SET_TEMPERATURE:
138
+    case OP_WAIT_TEMPERATURE:
139
+        debug("workflow temp %.1f C", wf[wf_i].steps[step].val / 10.0);
140
+        start_val = volcano_get_current_temp();
141
+        volcano_set_target_temp(wf[wf_i].steps[step].val);
142
+        break;
143
+
144
+    case OP_PUMP_TIME:
145
+        volcano_set_pump_state(true);
146
+        start_t = to_ms_since_boot(get_absolute_time());
147
+        start_val = 0;
148
+        debug("workflow pump %.3f s", wf[wf_i].steps[step].val / 1000.0);
149
+        break;
150
+
151
+    case OP_WAIT_TIME:
152
+        start_t = to_ms_since_boot(get_absolute_time());
153
+        start_val = 0;
154
+        debug("workflow time %.3f s", wf[wf_i].steps[step].val / 1000.0);
155
+        break;
156
+    }
157
+
158
+    curr_val = start_val;
159
+}
160
+
161
+uint16_t wf_count(void) {
162
+    return count;
163
+}
164
+
165
+const char *wf_name(uint16_t index) {
166
+    if (index >= count) {
167
+        debug("invalid index %d", index);
168
+        return NULL;
169
+    }
170
+    return wf[index].name;
171
+}
172
+
173
+const char *wf_author(uint16_t index) {
174
+    if (index >= count) {
175
+        debug("invalid index %d", index);
176
+        return NULL;
177
+    }
178
+    return wf[index].author;
179
+}
180
+
181
+struct wf_state wf_status(void) {
182
+    struct wf_state s = {
183
+        .status = status,
184
+        .index = step,
185
+        .count = wf[wf_i].count,
186
+        .step = wf[wf_i].steps[step],
187
+        .start_val = start_val,
188
+        .curr_val = curr_val,
189
+    };
190
+    return s;
191
+}
192
+
193
+void wf_start(uint16_t index) {
194
+    if (status != WF_IDLE) {
195
+        debug("workflow already running");
196
+        return;
197
+    }
198
+    if (index >= count) {
199
+        debug("invalid index %d", index);
200
+        return;
201
+    }
202
+
203
+    status = WF_RUNNING;
204
+    wf_i = index;
205
+    step = 0;
206
+
207
+    // discover characteristics
208
+    volcano_set_pump_state(false);
209
+    volcano_set_heater_state(false);
210
+    volcano_set_target_temp(1850);
211
+
212
+    volcano_set_heater_state(true);
213
+
214
+    do_step();
215
+}
216
+
217
+void wf_reset(void) {
218
+    status = WF_IDLE;
219
+}
220
+
221
+void wf_run(void) {
222
+    if (status == WF_IDLE) {
223
+        return;
224
+    }
225
+
226
+    bool done = false;
227
+
228
+    switch (wf[wf_i].steps[step].op) {
229
+    case OP_SET_TEMPERATURE:
230
+        done = true;
231
+        break;
232
+
233
+    case OP_WAIT_TEMPERATURE: {
234
+        uint16_t temp = volcano_get_current_temp();
235
+        curr_val = temp;
236
+        done = (temp >= (wf[wf_i].steps[step].val - 5));
237
+        break;
238
+    }
239
+
240
+    case OP_PUMP_TIME:
241
+    case OP_WAIT_TIME: {
242
+        uint32_t now = to_ms_since_boot(get_absolute_time());
243
+        uint32_t diff = now - start_t;
244
+        curr_val = diff;
245
+        done = (diff >= wf[wf_i].steps[step].val);
246
+        break;
247
+    }
248
+    }
249
+
250
+    if (done) {
251
+        if (wf[wf_i].steps[step].op == OP_PUMP_TIME) {
252
+            volcano_set_pump_state(false);
253
+        }
254
+
255
+        step++;
256
+        if (step >= wf[wf_i].count) {
257
+            status = WF_IDLE;
258
+            volcano_set_heater_state(false);
259
+            debug("workflow finished");
260
+        } else {
261
+            do_step();
262
+        }
263
+    }
264
+}

+ 1
- 0
st7789

@@ -0,0 +1 @@
1
+Subproject commit 1b211accf9d626666f0d4aad5edffba2791398a2

+ 16
- 0
web-app/fetch.sh Bestand weergeven

@@ -1,5 +1,21 @@
1 1
 #!/bin/bash
2 2
 
3
+# ----------------------------------------------------------------------------
4
+# Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+#
6
+# This program is free software: you can redistribute it and/or modify
7
+# it under the terms of the GNU General Public License as published by
8
+# the Free Software Foundation, either version 3 of the License, or
9
+# (at your option) any later version.
10
+#
11
+# This program is distributed in the hope that it will be useful,
12
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+# GNU General Public License for more details.
15
+#
16
+# See <http://www.gnu.org/licenses/>.
17
+# ----------------------------------------------------------------------------
18
+
3 19
 # https://github.com/beautify-web/js-beautify
4 20
 
5 21
 ROOT="https://app.storz-bickel.com"

Laden…
Annuleren
Opslaan