upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/main/market.c
diff options
context:
space:
mode:
Diffstat (limited to 'main/market.c')
-rw-r--r--main/market.c237
1 files changed, 237 insertions, 0 deletions
diff --git a/main/market.c b/main/market.c
new file mode 100644
index 0000000..c8a0b6d
--- /dev/null
+++ b/main/market.c
@@ -0,0 +1,237 @@
1#include "market.h"
2#include "beacon_price.h"
3#include "config.h"
4#include "identity.h"
5#include "esp_log.h"
6#include "esp_wifi.h"
7#include "freertos/FreeRTOS.h"
8#include "freertos/task.h"
9#include <string.h>
10
11static const char *TAG = "market";
12static market_t s_market;
13static bool s_initialized = false;
14
15static int64_t get_time_ms(void)
16{
17 return (int64_t)xTaskGetTickCount() * portTICK_PERIOD_MS;
18}
19
20static bool oui_matches(const uint8_t oui[3])
21{
22 return oui[0] == TOLLGATE_OUI_0 && oui[1] == TOLLGATE_OUI_1 && oui[2] == TOLLGATE_OUI_2;
23}
24
25static int find_entry_by_bssid(const uint8_t bssid[6])
26{
27 for (int i = 0; i < MARKET_MAX_ENTRIES; i++) {
28 if (s_market.entries[i].valid && memcmp(s_market.entries[i].bssid, bssid, 6) == 0) {
29 return i;
30 }
31 }
32 return -1;
33}
34
35static int find_free_slot(void)
36{
37 for (int i = 0; i < MARKET_MAX_ENTRIES; i++) {
38 if (!s_market.entries[i].valid) return i;
39 }
40 int oldest = 0;
41 int64_t oldest_time = s_market.entries[0].discovered_ms;
42 for (int i = 1; i < MARKET_MAX_ENTRIES; i++) {
43 if (s_market.entries[i].discovered_ms < oldest_time) {
44 oldest_time = s_market.entries[i].discovered_ms;
45 oldest = i;
46 }
47 }
48 return oldest;
49}
50
51void market_parse_vendor_ie(const uint8_t sa[6], const vendor_ie_data_t *ie, int rssi)
52{
53 if (!ie || ie->length < 4 + TOLLGATE_IE_PAYLOAD_SIZE) return;
54 if (!oui_matches(ie->vendor_oui)) return;
55 if (ie->vendor_oui_type != TOLLGATE_IE_TYPE) return;
56
57 const tollgate_price_payload_t *payload = (const tollgate_price_payload_t *)ie->payload;
58 if (payload->version != TOLLGATE_IE_VERSION) return;
59
60 const tollgate_identity_t *id = identity_get();
61 if (id && id->initialized) {
62 uint8_t my_npub_hash[4];
63 beacon_price_hash_npub(id->npub_hex, my_npub_hash);
64 if (memcmp(payload->npub_hash, my_npub_hash, 4) == 0) return;
65 }
66
67 int idx = find_entry_by_bssid(sa);
68 if (idx < 0) {
69 idx = find_free_slot();
70 if (s_market.count < MARKET_MAX_ENTRIES) s_market.count++;
71 }
72
73 market_entry_t *entry = &s_market.entries[idx];
74 memcpy(entry->bssid, sa, 6);
75 entry->rssi = (int8_t)rssi;
76 entry->price_per_step = payload->price_per_step;
77 entry->step_size = payload->step_size;
78 entry->metric = payload->metric;
79 memcpy(entry->mint_hash, payload->mint_hash, 4);
80 memcpy(entry->npub_hash, payload->npub_hash, 4);
81
82 uint8_t gh_len = payload->geohash_len;
83 if (gh_len > TOLLGATE_IE_GEOHASH_MAX) gh_len = TOLLGATE_IE_GEOHASH_MAX;
84 memcpy(entry->geohash, payload->geohash, gh_len);
85 entry->geohash[gh_len] = '\0';
86
87 entry->discovered_ms = get_time_ms();
88 entry->valid = true;
89 entry->ssid[0] = '\0';
90
91 ESP_LOGI(TAG, "Discovered TollGate %02X:%02X:%02X:%02X:%02X:%02X price=%lu sats step=%lu metric=%s RSSI=%d",
92 sa[0], sa[1], sa[2], sa[3], sa[4], sa[5],
93 (unsigned long)payload->price_per_step, (unsigned long)payload->step_size,
94 payload->metric ? "bytes" : "milliseconds", rssi);
95}
96
97static void vendor_ie_cb(void *ctx, wifi_vendor_ie_type_t type,
98 const uint8_t sa[6], const vendor_ie_data_t *vnd_ie, int rssi)
99{
100 (void)ctx;
101 (void)type;
102 if (!vnd_ie) return;
103 market_parse_vendor_ie(sa, vnd_ie, rssi);
104}
105
106static void scan_done_cb(void *arg, esp_event_base_t event_base,
107 int32_t event_id, void *event_data)
108{
109 (void)arg;
110 (void)event_base;
111 (void)event_id;
112 (void)event_data;
113
114 s_market.scanning = false;
115
116 uint16_t ap_count = 0;
117 esp_wifi_scan_get_ap_num(&ap_count);
118 if (ap_count == 0) return;
119
120 uint16_t max_aps = ap_count > 20 ? 20 : ap_count;
121 wifi_ap_record_t *ap_records = malloc(max_aps * sizeof(wifi_ap_record_t));
122 if (!ap_records) return;
123
124 esp_wifi_scan_get_ap_records(&max_aps, ap_records);
125
126 for (int i = 0; i < max_aps; i++) {
127 for (int j = 0; j < MARKET_MAX_ENTRIES; j++) {
128 if (!s_market.entries[j].valid) continue;
129 if (memcmp(s_market.entries[j].bssid, ap_records[i].bssid, 6) == 0) {
130 memcpy(s_market.entries[j].ssid, ap_records[i].ssid, 32);
131 s_market.entries[j].ssid[32] = '\0';
132 s_market.entries[j].rssi = ap_records[i].rssi;
133 break;
134 }
135 }
136 }
137 free(ap_records);
138 s_market.last_scan_ms = get_time_ms();
139
140 ESP_LOGI(TAG, "Scan complete: %d APs, %d TollGates found", max_aps, s_market.count);
141}
142
143static esp_event_handler_instance_t s_scan_done_handler = NULL;
144
145esp_err_t market_init(void)
146{
147 memset(&s_market, 0, sizeof(s_market));
148
149 esp_err_t ret = esp_wifi_set_vendor_ie_cb(vendor_ie_cb, NULL);
150 if (ret != ESP_OK) {
151 ESP_LOGE(TAG, "Failed to register vendor IE callback: %s", esp_err_to_name(ret));
152 return ret;
153 }
154
155 if (!s_scan_done_handler) {
156 ret = esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_SCAN_DONE,
157 scan_done_cb, NULL, &s_scan_done_handler);
158 if (ret != ESP_OK) {
159 ESP_LOGW(TAG, "Failed to register scan done handler: %s", esp_err_to_name(ret));
160 }
161 }
162
163 s_initialized = true;
164 ESP_LOGI(TAG, "Market scanner initialized");
165 return ESP_OK;
166}
167
168void market_tick(void)
169{
170 if (!s_initialized) return;
171
172 const tollgate_config_t *cfg = tollgate_config_get();
173 if (!cfg->market_enabled) return;
174
175 if (s_market.scanning) return;
176
177 int64_t now = get_time_ms();
178 int64_t elapsed = now - s_market.last_scan_ms;
179 int64_t interval_ms = (int64_t)cfg->market_scan_interval_s * 1000;
180 if (elapsed < interval_ms) return;
181
182 wifi_scan_config_t scan_config = {
183 .ssid = NULL,
184 .bssid = NULL,
185 .channel = 0,
186 .show_hidden = false,
187 .scan_type = WIFI_SCAN_TYPE_PASSIVE,
188 .scan_time.passive = 120,
189 };
190
191 esp_err_t ret = esp_wifi_scan_start(&scan_config, false);
192 if (ret == ESP_OK) {
193 s_market.scanning = true;
194 s_market.last_scan_ms = now;
195 s_market.consecutive_failures = 0;
196 ESP_LOGD(TAG, "Market scan started");
197 } else {
198 s_market.consecutive_failures++;
199 s_market.last_scan_ms = now;
200 if (s_market.consecutive_failures <= 3 || s_market.consecutive_failures % 30 == 0) {
201 ESP_LOGW(TAG, "Scan start failed: %s (failures: %d)", esp_err_to_name(ret), s_market.consecutive_failures);
202 }
203 }
204}
205
206const market_t *market_get(void)
207{
208 return &s_market;
209}
210
211int market_find_cheapest(void)
212{
213 int cheapest = -1;
214 uint32_t best_eff_price = UINT32_MAX;
215
216 for (int i = 0; i < MARKET_MAX_ENTRIES; i++) {
217 if (!s_market.entries[i].valid) continue;
218 if (s_market.entries[i].ssid[0] == '\0') continue;
219
220 uint32_t step = s_market.entries[i].step_size;
221 if (step == 0) continue;
222
223 uint32_t eff;
224 if (s_market.entries[i].metric == 0) {
225 eff = (uint32_t)s_market.entries[i].price_per_step * 60000 / step;
226 } else {
227 uint32_t eff_mb = (uint32_t)s_market.entries[i].price_per_step * 1048576 / step;
228 eff = eff_mb;
229 }
230
231 if (eff < best_eff_price) {
232 best_eff_price = eff;
233 cheapest = i;
234 }
235 }
236 return cheapest;
237}