42 Commits

Author SHA1 Message Date
  Thomas Buck 965cf087d8 print current value in workflow 8 months ago
  Thomas Buck ca91fdf394 only call disconnect once when aborting workflow 8 months ago
  Thomas Buck 33773f52d6 print workflow step and allow aborting 8 months ago
  Thomas Buck 0c2780e7ae allow going back from workflow selection 8 months ago
  Thomas Buck 5af9bc8780 add simplistic crafty support 8 months ago
  Thomas Buck 01259255e8 clear text box bg. fix ble disconnect and workflow notify. 8 months ago
  Thomas Buck e78ad09f27 add simple volcano_run state 8 months ago
  Thomas Buck f064eb3a5a generalize menu infrastructure to also use it for workflow selection 8 months ago
  Thomas Buck 5063ba3de1 add workflow logic 8 months ago
  Thomas Buck 00498af11e device selection menu 8 months ago
  Thomas Buck 0769496400 only show relevant devices in scan state 8 months ago
  Thomas Buck 00e30acf5d detect known device types and read their serial number 8 months ago
  Thomas Buck 03daa0ec1e test workflow in debug console 8 months ago
  Thomas Buck 722d6e6d8f add commands for heater and pump state 8 months ago
  Thomas Buck c3a43c4f91 proper timeouts for ble write, now working. 8 months ago
  Thomas Buck 46f9c9a5da serial console mostly working, but still losing some bytes when transmitting lots. 8 months ago
  Thomas Buck 3b40fe0f43 option to block on uart buffer overrun 8 months ago
  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. 8 months ago
  Thomas Buck 395ef608e6 implement ble service and characteristic discovery and value write for volcano. still crashing unfortunately. 8 months ago
  Thomas Buck c6da7c81a2 make inclusion of sources optional 8 months ago
  Thomas Buck 2354f36e7a tweak debug disk creation 8 months ago
  Thomas Buck 558224aa67 have the device give out its own source code 8 months ago
  Thomas Buck 17c4cf73ba run state machine for testing periodically. 8 months ago
  Thomas Buck 0d8e77dd69 optional auto connect for volcano console commands. 8 months ago
  Thomas Buck 1b3e3e286b implement ble read 8 months ago
  Thomas Buck 3f60298a6e only redraw battery indicator when needed 8 months ago
  Thomas Buck c376ba1486 ble connect and disconnect 8 months ago
  Thomas Buck 2c330fd03f basic state machine for app logic 8 months ago
  Thomas Buck d2c67b7c92 gap inquiry scan is not needed after all 8 months ago
  Thomas Buck 8e13f7b0d8 more work on ble scan. still not finding target device name, even with gap inquiry. 8 months ago
  Thomas Buck 822302d3e8 support arbitrary blended text colors 8 months ago
  Thomas Buck b7bae8f489 draw battery status on display 8 months ago
  Thomas Buck d7f8847dab optional background fill for text, for splash screen 8 months ago
  Thomas Buck 6a4c2b8e16 first text rendering test 8 months ago
  Thomas Buck 86e238560d lcd working, drawing logo as splash screen 8 months ago
  Thomas Buck 47d270deb2 add ble scanning test 8 months ago
  Thomas Buck 25aa485d8f add lipo shim reading 8 months ago
  Thomas Buck a9bf35e24b pico w onboard led 8 months ago
  Thomas Buck 0b7fcb080b first building but not running state of C Pico SDK version 8 months ago
  Thomas Buck 76436adf41 unfinished py ota 8 months ago
  Thomas Buck 8c9fcb659d modify brightness in menus. nicer battery display. 8 months ago
  Thomas Buck 18b4c98c5e gpl license 9 months ago
93 changed files with 11958 additions and 31 deletions
  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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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
File diff suppressed because it is too large
View File


+ 24
- 0
include/main.h View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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"

Loading…
Cancel
Save